From 7c965ced9b4d8cab2f7f5a1951952d26706e8197 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 24 Aug 2021 16:27:41 +0200 Subject: [PATCH 001/266] Factory: fixed extended interfaces [Closes #87] --- src/PhpGenerator/Factory.php | 6 +++++- tests/PhpGenerator/ClassType.from.phpt | 2 ++ tests/PhpGenerator/expected/ClassType.from.expect | 8 ++++++++ tests/PhpGenerator/fixtures/classes.php | 8 ++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index c87f4d02..68cd3b97 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -37,7 +37,11 @@ public function fromClassReflection(\ReflectionClass $from, bool $withBodies = f return !is_subclass_of($iface, $item); }); } - $class->setImplements($ifaces); + if ($from->isInterface()) { + $class->setExtends($ifaces); + } else { + $class->setImplements($ifaces); + } $class->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); $class->setAttributes(self::getAttributes($from)); diff --git a/tests/PhpGenerator/ClassType.from.phpt b/tests/PhpGenerator/ClassType.from.phpt index 2daa5b60..221d02b1 100644 --- a/tests/PhpGenerator/ClassType.from.phpt +++ b/tests/PhpGenerator/ClassType.from.phpt @@ -15,6 +15,8 @@ require __DIR__ . '/../bootstrap.php'; $res[] = ClassType::from(Abc\Interface1::class); $res[] = ClassType::from(Abc\Interface2::class); +$res[] = ClassType::from(Abc\Interface3::class); +$res[] = ClassType::from(Abc\Interface4::class); $res[] = ClassType::from(Abc\Class1::class); $res[] = ClassType::from(new Abc\Class2); $obj = new Abc\Class3; diff --git a/tests/PhpGenerator/expected/ClassType.from.expect b/tests/PhpGenerator/expected/ClassType.from.expect index 8aeb3f8d..4b371f98 100644 --- a/tests/PhpGenerator/expected/ClassType.from.expect +++ b/tests/PhpGenerator/expected/ClassType.from.expect @@ -11,6 +11,14 @@ interface Interface2 { } +interface Interface3 extends Interface1 +{ +} + +interface Interface4 extends Interface3, Interface2 +{ +} + abstract class Class1 implements Interface1 { /** diff --git a/tests/PhpGenerator/fixtures/classes.php b/tests/PhpGenerator/fixtures/classes.php index 1fae4ab3..38be518c 100644 --- a/tests/PhpGenerator/fixtures/classes.php +++ b/tests/PhpGenerator/fixtures/classes.php @@ -19,6 +19,14 @@ interface Interface2 } +interface Interface3 extends Interface1 +{ +} + +interface Interface4 extends Interface3, Interface2 +{ +} + abstract class Class1 implements Interface1 { /** @return Class1 */ From 605a107b5c77d64e67fb33d32ebb8f04e8b32182 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 24 Aug 2021 15:30:33 +0200 Subject: [PATCH 002/266] tests: improved, fixed optional parameters before required parameters --- tests/PhpGenerator/Dumper.dump().errors.phpt | 29 ++++++++ tests/PhpGenerator/Dumper.dump().phpt | 68 ++++++++----------- .../expected/ClassType.from.expect | 18 +++-- tests/PhpGenerator/fixtures/classes.php | 9 ++- 4 files changed, 71 insertions(+), 53 deletions(-) create mode 100644 tests/PhpGenerator/Dumper.dump().errors.phpt diff --git a/tests/PhpGenerator/Dumper.dump().errors.phpt b/tests/PhpGenerator/Dumper.dump().errors.phpt new file mode 100644 index 00000000..a5b5dd59 --- /dev/null +++ b/tests/PhpGenerator/Dumper.dump().errors.phpt @@ -0,0 +1,29 @@ +dump($rec); +}, Nette\InvalidArgumentException::class, 'Nesting level too deep or recursive dependency.'); + + +Assert::exception(function () { + $rec = new stdClass; + $rec->x = &$rec; + $dumper = new Dumper; + $dumper->dump($rec); +}, Nette\InvalidArgumentException::class, 'Nesting level too deep or recursive dependency.'); diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index ede092ca..93ae26d2 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -16,6 +16,8 @@ require __DIR__ . '/../bootstrap.php'; ini_set('serialize_precision', '14'); $dumper = new Dumper; + +// scalars Assert::same('0', $dumper->dump(0)); Assert::same('1', $dumper->dump(1)); Assert::same('0.0', $dumper->dump(0.0)); @@ -38,9 +40,9 @@ Assert::same( Assert::same('"\rHello \$"', $dumper->dump("\rHello $")); Assert::same("'He\\llo'", $dumper->dump('He\llo')); Assert::same('\'He\ll\\\\\o \\\'wor\\\\\\\'ld\\\\\'', $dumper->dump('He\ll\\\o \'wor\\\'ld\\')); -Assert::same('[]', $dumper->dump([])); -// internal classes + +// literal Assert::same('[$s]', $dumper->dump([new PhpLiteral('$s')])); same('[ function () { @@ -48,6 +50,9 @@ same('[ }, ]', $dumper->dump([(new Nette\PhpGenerator\Closure)->setBody('return 1;')])); + +// arrays +Assert::same('[]', $dumper->dump([])); Assert::same('[1, 2, 3]', $dumper->dump([1, 2, 3])); Assert::same("['a']", $dumper->dump(['a'])); Assert::same("[2 => 'a']", $dumper->dump([2 => 'a'])); @@ -56,19 +61,8 @@ Assert::same("[-2 => 'a', -1 => 'b']", $dumper->dump([-2 => 'a', -1 => 'b'])); Assert::same("[-2 => 'a', 0 => 'b']", $dumper->dump([-2 => 'a', 0 => 'b'])); Assert::same("[0 => 'a', -2 => 'b', 1 => 'c']", $dumper->dump(['a', -2 => 'b', 1 => 'c'])); -$dumper->wrapLength = 100; -same("[ - [ - 'a', - 'loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong', - ], -]", $dumper->dump([['a', 'loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong']])); - -Assert::same( - "['a' => 1, 0 => [\"\\r\" => \"\\r\", 0 => 2], 1 => 3]", - $dumper->dump(['a' => 1, ["\r" => "\r", 2], 3]) -); +// stdClass Assert::same( "(object) [\n\t'a' => 1,\n\t'b' => 2,\n]", $dumper->dump((object) ['a' => 1, 'b' => 2]) @@ -80,6 +74,7 @@ Assert::same( ); +// objects class Test { public $a = 1; @@ -121,6 +116,23 @@ Assert::same( Assert::equal(new Test2, eval('return ' . $dumper->dump(new Test2) . ';')); +Assert::exception(function () { + $dumper = new Dumper; + $dumper->dump(new class { + }); +}, Nette\InvalidArgumentException::class, 'Cannot dump anonymous class.'); + + + +// closures +Assert::exception(function () { + $dumper = new Dumper; + $dumper->dump(function () {}); +}, Nette\InvalidArgumentException::class, 'Cannot dump closure.'); + + + +// serializable class Test3 implements Serializable { private $a; @@ -140,13 +152,9 @@ class Test3 implements Serializable Assert::same('unserialize(\'C:5:"Test3":0:{}\')', $dumper->dump(new Test3)); Assert::equal(new Test3, eval('return ' . $dumper->dump(new Test3) . ';')); -Assert::exception(function () { - $dumper = new Dumper; - $dumper->dump(function () {}); -}, Nette\InvalidArgumentException::class, 'Cannot dump closure.'); - +// datetime class TestDateTime extends DateTime { } @@ -167,25 +175,3 @@ same( ])", $dumper->dump(new TestDateTime('2016-06-22 20:52:43.1234', new DateTimeZone('Europe/Prague'))) ); - -Assert::exception(function () { - $dumper = new Dumper; - $dumper->dump(new class { - }); -}, Nette\InvalidArgumentException::class, 'Cannot dump anonymous class.'); - - -Assert::exception(function () { - $rec = []; - $rec[] = &$rec; - $dumper = new Dumper; - $dumper->dump($rec); -}, Nette\InvalidArgumentException::class, 'Nesting level too deep or recursive dependency.'); - - -Assert::exception(function () { - $rec = new stdClass; - $rec->x = &$rec; - $dumper = new Dumper; - $dumper->dump($rec); -}, Nette\InvalidArgumentException::class, 'Nesting level too deep or recursive dependency.'); diff --git a/tests/PhpGenerator/expected/ClassType.from.expect b/tests/PhpGenerator/expected/ClassType.from.expect index 4b371f98..fa1d5120 100644 --- a/tests/PhpGenerator/expected/ClassType.from.expect +++ b/tests/PhpGenerator/expected/ClassType.from.expect @@ -50,15 +50,13 @@ class Class2 extends Class1 implements Interface2 * Func3 * @return Class1 */ - private function &func3( - array $a = [], - Class2 $b = null, - Unknown $c, - \Xyz\Unknown $d, - callable $e, - $f = Abc\Unknown::ABC, - $g - ) { + private function &func3(array $a, Class2 $b, Unknown $c, \Xyz\Unknown $d, ?callable $e, $f) + { + } + + + private function func4(array $a = [], Class2 $b = null, $c = Abc\Unknown::ABC) + { } @@ -79,7 +77,7 @@ class Class4 class Class5 { - public function func1(\A $a, ?\B $b, \C $c = null, \D $d = null, \E $e, ?int $i = 1, ?array $arr = []) + public function func1(\A $a, ?\B $b, \C $c = null, \D $d = null, ?int $i = 1, ?array $arr = []) { } diff --git a/tests/PhpGenerator/fixtures/classes.php b/tests/PhpGenerator/fixtures/classes.php index 38be518c..f96ae977 100644 --- a/tests/PhpGenerator/fixtures/classes.php +++ b/tests/PhpGenerator/fixtures/classes.php @@ -59,7 +59,12 @@ class Class2 extends Class1 implements Interface2 * Func3 * @return Class1 */ - private function &func3(array $a = [], Class2 $b = null, \Abc\Unknown $c, \Xyz\Unknown $d, callable $e, $f = Unknown::ABC, $g) + private function &func3(array $a, Class2 $b, \Abc\Unknown $c, \Xyz\Unknown $d, ?callable $e, $f) + { + } + + + private function func4(array $a = [], Class2 $b = null, $c = Unknown::ABC) { } @@ -84,7 +89,7 @@ class Class4 /** */ class Class5 { - public function func1(\A $a, ?\B $b, ?\C $c = null, \D $d = null, \E $e, ?int $i = 1, ?array $arr = []) + public function func1(\A $a, ?\B $b, ?\C $c = null, \D $d = null, ?int $i = 1, ?array $arr = []) { } From aab1e13749445650b8d20a77c184a6c5679c9c4c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 25 Aug 2021 12:21:16 +0200 Subject: [PATCH 003/266] tests: PhpLiteral -> Literal --- tests/PhpGenerator/ClassType.attributes.phpt | 4 ++-- tests/PhpGenerator/ClassType.phpt | 8 ++++---- tests/PhpGenerator/Dumper.dump().phpt | 4 ++-- tests/PhpGenerator/Dumper.dump().wrap.phpt | 6 +++--- tests/PhpGenerator/Printer.phpt | 6 +++--- tests/PhpGenerator/PsrPrinter.phpt | 6 +++--- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/PhpGenerator/ClassType.attributes.phpt b/tests/PhpGenerator/ClassType.attributes.phpt index 42d2991f..bcfaf4c2 100644 --- a/tests/PhpGenerator/ClassType.attributes.phpt +++ b/tests/PhpGenerator/ClassType.attributes.phpt @@ -7,7 +7,7 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; -use Nette\PhpGenerator\PhpLiteral; +use Nette\PhpGenerator\Literal; require __DIR__ . '/../bootstrap.php'; @@ -18,7 +18,7 @@ $class = new ClassType('Example'); $class ->addComment('Description of class.') ->addAttribute('ExampleAttribute') - ->addAttribute('WithArgument', [new PhpLiteral('Foo::BAR')]) + ->addAttribute('WithArgument', [new Literal('Foo::BAR')]) ->addAttribute('NamedArguments', ['foo' => 'bar', 'bar' => [1, 2, 3]]); $class->addConstant('FOO', 123) diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index 53379b23..c0605999 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -7,7 +7,7 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; -use Nette\PhpGenerator\PhpLiteral; +use Nette\PhpGenerator\Literal; use Nette\PhpGenerator\Type; use Tester\Assert; @@ -46,7 +46,7 @@ Assert::same(['ObjectTrait' => [], 'AnotherTrait' => ['sayHello as protected']], Assert::count(2, $class->getConstants()); Assert::type(Nette\PhpGenerator\Constant::class, $class->getConstants()['ROLE']); -$class->addConstant('FORCE_ARRAY', new PhpLiteral('Nette\Utils\Json::FORCE_ARRAY')) +$class->addConstant('FORCE_ARRAY', new Literal('Nette\Utils\Json::FORCE_ARRAY')) ->setVisibility('private') ->addComment('Commented'); @@ -55,7 +55,7 @@ $class->addProperty('handle') ->addComment('@var resource orignal file handle'); $class->addProperty('order') - ->setValue(new PhpLiteral('RecursiveIteratorIterator::SELF_FIRST')); + ->setValue(new Literal('RecursiveIteratorIterator::SELF_FIRST')); $class->addProperty('typed1') ->setType(Type::ARRAY); @@ -103,7 +103,7 @@ $m = $class->addMethod('getSections') ->setReturnReference(true) ->addBody('$mode = ?;', [123]) ->addBody('return self::$sections;'); -$m->addParameter('mode', new PhpLiteral('self::ORDER')); +$m->addParameter('mode', new Literal('self::ORDER')); Assert::false($m->isFinal()); Assert::true($m->isStatic()); diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index 93ae26d2..61892164 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -7,7 +7,7 @@ declare(strict_types=1); use Nette\PhpGenerator\Dumper; -use Nette\PhpGenerator\PhpLiteral; +use Nette\PhpGenerator\Literal; use Tester\Assert; @@ -43,7 +43,7 @@ Assert::same('\'He\ll\\\\\o \\\'wor\\\\\\\'ld\\\\\'', $dumper->dump('He\ll\\\o \ // literal -Assert::same('[$s]', $dumper->dump([new PhpLiteral('$s')])); +Assert::same('[$s]', $dumper->dump([new Literal('$s')])); same('[ function () { return 1; diff --git a/tests/PhpGenerator/Dumper.dump().wrap.phpt b/tests/PhpGenerator/Dumper.dump().wrap.phpt index 9d9ffb14..56452d12 100644 --- a/tests/PhpGenerator/Dumper.dump().wrap.phpt +++ b/tests/PhpGenerator/Dumper.dump().wrap.phpt @@ -7,7 +7,7 @@ declare(strict_types=1); use Nette\PhpGenerator\Dumper; -use Nette\PhpGenerator\PhpLiteral; +use Nette\PhpGenerator\Literal; require __DIR__ . '/../bootstrap.php'; @@ -38,8 +38,8 @@ same( ], ]", $dumper->dump([ - 'single' => new PhpLiteral('1 + 2'), - 'multi' => new PhpLiteral("[\n\t1,\n]\n"), + 'single' => new Literal('1 + 2'), + 'multi' => new Literal("[\n\t1,\n]\n"), ]) ); diff --git a/tests/PhpGenerator/Printer.phpt b/tests/PhpGenerator/Printer.phpt index 0e77fd24..7ffba9a1 100644 --- a/tests/PhpGenerator/Printer.phpt +++ b/tests/PhpGenerator/Printer.phpt @@ -3,7 +3,7 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; -use Nette\PhpGenerator\PhpLiteral; +use Nette\PhpGenerator\Literal; use Nette\PhpGenerator\Printer; use Tester\Assert; @@ -22,7 +22,7 @@ $class = (new ClassType('Example')) ->addTrait('AnotherTrait', ['sayHello as protected']) ->addComment("Description of class.\nThis is example\n"); -$class->addConstant('FORCE_ARRAY', new PhpLiteral('Nette\Utils\Json::FORCE_ARRAY')) +$class->addConstant('FORCE_ARRAY', new Literal('Nette\Utils\Json::FORCE_ARRAY')) ->setPrivate() ->addComment('Commented'); @@ -34,7 +34,7 @@ $class->addProperty('handle') ->addComment('@var resource orignal file handle'); $class->addProperty('order') - ->setValue(new PhpLiteral('RecursiveIteratorIterator::SELF_FIRST')); + ->setValue(new Literal('RecursiveIteratorIterator::SELF_FIRST')); $class->addProperty('multilineLong', ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, 'dddddddd' => 4, 'eeeeeeee' => 5, 'ffffffff' => 6]); $class->addProperty('short', ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, 'dddddddd' => 4, 'eeeeeeee' => 5, 'ffffffff' => 6]); diff --git a/tests/PhpGenerator/PsrPrinter.phpt b/tests/PhpGenerator/PsrPrinter.phpt index 8dd5b3c8..4f2d1464 100644 --- a/tests/PhpGenerator/PsrPrinter.phpt +++ b/tests/PhpGenerator/PsrPrinter.phpt @@ -3,7 +3,7 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; -use Nette\PhpGenerator\PhpLiteral; +use Nette\PhpGenerator\Literal; use Nette\PhpGenerator\PsrPrinter; @@ -21,7 +21,7 @@ $class = (new ClassType('Example')) ->addTrait('AnotherTrait', ['sayHello as protected']) ->addComment("Description of class.\nThis is example\n"); -$class->addConstant('FORCE_ARRAY', new PhpLiteral('Nette\Utils\Json::FORCE_ARRAY')) +$class->addConstant('FORCE_ARRAY', new Literal('Nette\Utils\Json::FORCE_ARRAY')) ->setVisibility('private') ->addComment('Commented'); @@ -33,7 +33,7 @@ $class->addProperty('handle') ->addComment('@var resource orignal file handle'); $class->addProperty('order') - ->setValue(new PhpLiteral('RecursiveIteratorIterator::SELF_FIRST')); + ->setValue(new Literal('RecursiveIteratorIterator::SELF_FIRST')); $class->addProperty('multilineLong', ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, 'dddddddd' => 4, 'eeeeeeee' => 5, 'ffffffff' => 6]); $class->addProperty('short', ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, 'dddddddd' => 4, 'eeeeeeee' => 5, 'ffffffff' => 6]); From e8a70d92cbc151078e097cec78d77b4ec12fe553 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 29 Aug 2021 17:42:24 +0200 Subject: [PATCH 004/266] typo --- src/PhpGenerator/Helpers.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 82ede77a..caad70b5 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -22,21 +22,21 @@ final class Helpers public const PHP_IDENT = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'; - /** @deprecated use Nette\PhpGenerator\Dumper::dump() */ + /** @deprecated use (new Nette\PhpGenerator\Dumper)->dump() */ public static function dump($var): string { return (new Dumper)->dump($var); } - /** @deprecated use Nette\PhpGenerator\Dumper::format() */ + /** @deprecated use (new Nette\PhpGenerator\Dumper)->format() */ public static function format(string $statement, ...$args): string { return (new Dumper)->format($statement, ...$args); } - /** @deprecated use Nette\PhpGenerator\Dumper::format() */ + /** @deprecated use (new Nette\PhpGenerator\Dumper)->format() */ public static function formatArgs(string $statement, array $args): string { return (new Dumper)->format($statement, ...$args); From 022ae4d2982eaefed6720b624395a43176041481 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 24 Aug 2021 22:28:45 +0200 Subject: [PATCH 005/266] opened 3.6-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 75c72f4c..3294f5d9 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.5-dev" + "dev-master": "3.6-dev" } } } From eda0cec61346693c1738acee6b6a9f8baa2a7611 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 25 Aug 2021 00:31:29 +0200 Subject: [PATCH 006/266] requires PHP 7.2 --- .github/workflows/coding-style.yml | 2 +- .github/workflows/tests.yml | 4 ++-- composer.json | 2 +- readme.md | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index d415d813..c1ce1d99 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v1 with: - php-version: 7.1 + php-version: 7.2 coverage: none - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 73516285..01c7894f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.1', '7.2', '7.3', '7.4', '8.0'] + php: ['7.2', '7.3', '7.4', '8.0'] fail-fast: false @@ -35,7 +35,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.1 + php-version: 7.2 coverage: none - run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable diff --git a/composer.json b/composer.json index 3294f5d9..b7aec1a4 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=7.1", + "php": ">=7.2", "nette/utils": "^3.1.2" }, "require-dev": { diff --git a/readme.md b/readme.md index 04861b82..2140c672 100644 --- a/readme.md +++ b/readme.md @@ -33,6 +33,7 @@ Installation composer require nette/php-generator ``` +- PhpGenerator 3.6 is compatible with PHP 7.2 to 8.0 - PhpGenerator 3.2 – 3.5 is compatible with PHP 7.1 to 8.0 - PhpGenerator 3.1 is compatible with PHP 7.1 to 7.3 - PhpGenerator 3.0 is compatible with PHP 7.0 to 7.3 From 3122e00d66f041fec5f8015cf1932cb188cb784a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 24 Aug 2021 22:57:14 +0200 Subject: [PATCH 007/266] ClassType: setInterface() & setTrait() replaced with interface() & trait() factories --- readme.md | 8 ++++---- src/PhpGenerator/ClassType.php | 20 +++++++++++++++++++- src/PhpGenerator/PhpNamespace.php | 4 ++-- tests/PhpGenerator/ClassType.interface.phpt | 3 +-- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/readme.md b/readme.md index 2140c672..7345464b 100644 --- a/readme.md +++ b/readme.md @@ -219,12 +219,12 @@ echo $printer->printClass($class); // 4 spaces indentation Interface or Trait ------------------ -You can create interfaces and traits in a similar way, just change the type: +You can create interfaces and traits: ```php -$class = new Nette\PhpGenerator\ClassType('DemoInterface'); -$class->setInterface(); -// or $class->setTrait(); +$interface = Nette\PhpGenerator\ClassType::interface('MyInterface'); +$trait = Nette\PhpGenerator\ClassType::trait('MyTrait'); +// in a similar way $class = Nette\PhpGenerator\ClassType::class('MyClass'); ``` Literals diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 5d3b719f..00dd092f 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -68,6 +68,24 @@ final class ClassType private $methods = []; + public static function class(string $name = null, PhpNamespace $namespace = null): self + { + return new self($name, $namespace); + } + + + public static function interface(string $name = null, PhpNamespace $namespace = null): self + { + return (new self($name, $namespace))->setType(self::TYPE_INTERFACE); + } + + + public static function trait(string $name = null, PhpNamespace $namespace = null): self + { + return (new self($name, $namespace))->setType(self::TYPE_TRAIT); + } + + /** * @param string|object $class */ @@ -131,7 +149,7 @@ public function getName(): ?string } - /** @return static */ + /** @deprecated */ public function setClass(): self { $this->type = self::TYPE_CLASS; diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 8ef230a6..50f8b5bb 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -179,13 +179,13 @@ public function addClass(string $name): ClassType public function addInterface(string $name): ClassType { - return $this->addClass($name)->setInterface(); + return $this->addClass($name)->setType(ClassType::TYPE_INTERFACE); } public function addTrait(string $name): ClassType { - return $this->addClass($name)->setTrait(); + return $this->addClass($name)->setType(ClassType::TYPE_TRAIT); } diff --git a/tests/PhpGenerator/ClassType.interface.phpt b/tests/PhpGenerator/ClassType.interface.phpt index b89ed6b2..4e8641dd 100644 --- a/tests/PhpGenerator/ClassType.interface.phpt +++ b/tests/PhpGenerator/ClassType.interface.phpt @@ -13,9 +13,8 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$interface = new ClassType('IExample'); +$interface = ClassType::interface('IExample'); $interface - ->setInterface() ->addExtend('IOne') ->addExtend('ITwo') ->addComment('Description of interface'); From bb8ce955273f67bd684633aaeb0034af3967216c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 25 Aug 2021 12:05:14 +0200 Subject: [PATCH 008/266] support for PHP 8.1 --- .github/workflows/tests.yml | 2 +- composer.json | 2 +- readme.md | 2 +- tests/PhpGenerator/Dumper.dump().phpt | 30 +++++++++++++++------------ 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 01c7894f..266902db 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.2', '7.3', '7.4', '8.0'] + php: ['7.2', '7.3', '7.4', '8.0', '8.1'] fail-fast: false diff --git a/composer.json b/composer.json index b7aec1a4..ca86f621 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=7.2", + "php": ">=7.2 <8.2", "nette/utils": "^3.1.2" }, "require-dev": { diff --git a/readme.md b/readme.md index 7345464b..e8f7f478 100644 --- a/readme.md +++ b/readme.md @@ -33,7 +33,7 @@ Installation composer require nette/php-generator ``` -- PhpGenerator 3.6 is compatible with PHP 7.2 to 8.0 +- PhpGenerator 3.6 is compatible with PHP 7.2 to 8.1 - PhpGenerator 3.2 – 3.5 is compatible with PHP 7.1 to 8.0 - PhpGenerator 3.1 is compatible with PHP 7.1 to 7.3 - PhpGenerator 3.0 is compatible with PHP 7.0 to 7.3 diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index 61892164..3106527b 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -110,7 +110,9 @@ class Test2 extends Test } Assert::same( - "\\Nette\\PhpGenerator\\Dumper::createObject('Test2', [\n\t\"\\x00Test2\\x00c\" => 4,\n\t'a' => 1,\n\t\"\\x00*\\x00b\" => 2,\n])", + PHP_VERSION_ID < 80100 + ? "\\Nette\\PhpGenerator\\Dumper::createObject('Test2', [\n\t\"\\x00Test2\\x00c\" => 4,\n\t'a' => 1,\n\t\"\\x00*\\x00b\" => 2,\n])" + : "\\Nette\\PhpGenerator\\Dumper::createObject('Test2', [\n\t'a' => 1,\n\t\"\\x00*\\x00b\" => 2,\n\t\"\\x00Test2\\x00c\" => 4,\n])", $dumper->dump(new Test2) ); Assert::equal(new Test2, eval('return ' . $dumper->dump(new Test2) . ';')); @@ -133,24 +135,26 @@ Assert::exception(function () { // serializable -class Test3 implements Serializable -{ - private $a; +if (PHP_VERSION_ID < 80100) { + class Test3 implements Serializable + { + private $a; - public function serialize() - { - return ''; - } + public function serialize() + { + return ''; + } - public function unserialize($s) - { + public function unserialize($s) + { + } } -} -Assert::same('unserialize(\'C:5:"Test3":0:{}\')', $dumper->dump(new Test3)); -Assert::equal(new Test3, eval('return ' . $dumper->dump(new Test3) . ';')); + Assert::same('unserialize(\'C:5:"Test3":0:{}\')', $dumper->dump(new Test3)); + Assert::equal(new Test3, eval('return ' . $dumper->dump(new Test3) . ';')); +} From ee2515816d9559df52e20341386d02c16f3b42e7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 24 Aug 2021 17:23:57 +0200 Subject: [PATCH 009/266] added 'never' built-in type --- src/PhpGenerator/PhpNamespace.php | 2 +- src/PhpGenerator/Type.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 50f8b5bb..64ca1f4c 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -29,7 +29,7 @@ final class PhpNamespace private const KEYWORDS = [ 'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1, 'callable' => 1, 'iterable' => 1, 'void' => 1, 'self' => 1, 'parent' => 1, 'static' => 1, - 'mixed' => 1, 'null' => 1, 'false' => 1, + 'mixed' => 1, 'null' => 1, 'false' => 1, 'never' => 1, ]; /** @var string */ diff --git a/src/PhpGenerator/Type.php b/src/PhpGenerator/Type.php index d320bf3e..88e4b663 100644 --- a/src/PhpGenerator/Type.php +++ b/src/PhpGenerator/Type.php @@ -25,6 +25,7 @@ class Type CALLABLE = 'callable', ITERABLE = 'iterable', VOID = 'void', + NEVER = 'never', MIXED = 'mixed', FALSE = 'false', NULL = 'null', From e31ad9efeeecfb86e077a671fec8d0559afdcf3f Mon Sep 17 00:00:00 2001 From: iggyvolz Date: Tue, 24 Aug 2021 17:29:02 +0200 Subject: [PATCH 010/266] added support for enums --- composer.json | 2 +- ecs.php | 1 + readme.md | 37 ++++++++++ src/PhpGenerator/ClassType.php | 73 +++++++++++++++++-- src/PhpGenerator/Dumper.php | 3 + src/PhpGenerator/EnumCase.php | 41 +++++++++++ src/PhpGenerator/Factory.php | 42 +++++++++-- src/PhpGenerator/PhpFile.php | 8 ++ src/PhpGenerator/PhpNamespace.php | 6 ++ src/PhpGenerator/Printer.php | 15 +++- tests/PhpGenerator/ClassType.addMember.phpt | 2 +- tests/PhpGenerator/ClassType.enum.phpt | 48 ++++++++++++ tests/PhpGenerator/ClassType.from.enum.phpt | 18 +++++ tests/PhpGenerator/Dumper.dump().enum.phpt | 25 +++++++ tests/PhpGenerator/PhpFile.phpt | 3 + .../expected/ClassType.enum.expect | 31 ++++++++ .../expected/ClassType.from.enum.expect | 31 ++++++++ .../expected/PhpFile.bracketed.expect | 4 + .../expected/PhpFile.regular.expect | 4 + tests/PhpGenerator/fixtures/enum.php81 | 37 ++++++++++ 20 files changed, 415 insertions(+), 16 deletions(-) create mode 100644 src/PhpGenerator/EnumCase.php create mode 100644 tests/PhpGenerator/ClassType.enum.phpt create mode 100644 tests/PhpGenerator/ClassType.from.enum.phpt create mode 100644 tests/PhpGenerator/Dumper.dump().enum.phpt create mode 100644 tests/PhpGenerator/expected/ClassType.enum.expect create mode 100644 tests/PhpGenerator/expected/ClassType.from.enum.expect create mode 100644 tests/PhpGenerator/fixtures/enum.php81 diff --git a/composer.json b/composer.json index ca86f621..cd4cf1e4 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.0 features.", + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.1 features.", "keywords": ["nette", "php", "code", "scaffolding"], "homepage": "https://nette.org", "license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"], diff --git a/ecs.php b/ecs.php index 40851e2a..83fd07b9 100644 --- a/ecs.php +++ b/ecs.php @@ -15,6 +15,7 @@ $parameters->set('skip', [ 'fixtures*/*', + 'tests/PhpGenerator/Dumper.dump().enum.phpt', // enum // constant NULL, FALSE PhpCsFixer\Fixer\Casing\LowercaseConstantsFixer::class => [ diff --git a/readme.md b/readme.md index e8f7f478..0de5e62f 100644 --- a/readme.md +++ b/readme.md @@ -227,6 +227,43 @@ $trait = Nette\PhpGenerator\ClassType::trait('MyTrait'); // in a similar way $class = Nette\PhpGenerator\ClassType::class('MyClass'); ``` +Enums +----- + +You can easily create the enums that PHP 8.1 brings: + +```php +$enum = Nette\PhpGenerator\ClassType::enum('Suit'); +$enum->addCase('Clubs'); +$enum->addCase('Diamonds'); +$enum->addCase('Hearts'); +$enum->addCase('Spades'); + +echo $enum; +``` + +Result: + +```php +enum Suit +{ + case Clubs; + case Diamonds; + case Hearts; + case Spades; +} +``` + +You can also define scalar equivalents for cases to create a backed enum: + +```php +$enum->addCase('Clubs', '♣'); +$enum->addCase('Diamonds', '♦'); +``` + +It is possible to add a comment or [attributes](#attributes) to each case using `addComment()` or `addAttribute()`. + + Literals -------- diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 00dd092f..6e0e297c 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -13,7 +13,7 @@ /** - * Class/Interface/Trait description. + * Class/Interface/Trait/Enum description. * * @property Method[] $methods * @property Property[] $properties @@ -27,7 +27,8 @@ final class ClassType public const TYPE_CLASS = 'class', TYPE_INTERFACE = 'interface', - TYPE_TRAIT = 'trait'; + TYPE_TRAIT = 'trait', + TYPE_ENUM = 'enum'; public const VISIBILITY_PUBLIC = 'public', @@ -67,6 +68,9 @@ final class ClassType /** @var Method[] name => Method */ private $methods = []; + /** @var EnumCase[] name => EnumCase */ + private $cases = []; + public static function class(string $name = null, PhpNamespace $namespace = null): self { @@ -86,6 +90,12 @@ public static function trait(string $name = null, PhpNamespace $namespace = null } + public static function enum(string $name = null, PhpNamespace $namespace = null): self + { + return (new self($name, $namespace))->setType(self::TYPE_ENUM); + } + + /** * @param string|object $class */ @@ -191,11 +201,17 @@ public function isTrait(): bool } + public function isEnum(): bool + { + return $this->type === self::TYPE_ENUM; + } + + /** @return static */ public function setType(string $type): self { - if (!in_array($type, [self::TYPE_CLASS, self::TYPE_INTERFACE, self::TYPE_TRAIT], true)) { - throw new Nette\InvalidArgumentException('Argument must be class|interface|trait.'); + if (!in_array($type, [self::TYPE_CLASS, self::TYPE_INTERFACE, self::TYPE_TRAIT, self::TYPE_ENUM], true)) { + throw new Nette\InvalidArgumentException('Argument must be class|interface|trait|enum.'); } $this->type = $type; return $this; @@ -351,7 +367,7 @@ public function removeTrait(string $name): self /** - * @param Method|Property|Constant $member + * @param Method|Property|Constant|EnumCase $member * @return static */ public function addMember($member): self @@ -368,8 +384,11 @@ public function addMember($member): self } elseif ($member instanceof Constant) { $this->consts[$member->getName()] = $member; + } elseif ($member instanceof EnumCase) { + $this->cases[$member->getName()] = $member; + } else { - throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant.'); + throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant|EnumCase.'); } return $this; @@ -414,6 +433,44 @@ public function removeConstant(string $name): self } + /** + * Sets cases to enum + * @param EnumCase[] $consts + * @return static + */ + public function setCases(array $cases): self + { + (function (EnumCase ...$cases) {})(...$cases); + $this->cases = []; + foreach ($cases as $case) { + $this->cases[$case->getName()] = $case; + } + return $this; + } + + + /** @return EnumCase[] */ + public function getCases(): array + { + return $this->cases; + } + + + /** Adds case to enum */ + public function addCase(string $name, $value = null): EnumCase + { + return $this->cases[$name] = (new EnumCase($name))->setValue($value); + } + + + /** @return static */ + public function removeCase(string $name): self + { + unset($this->cases[$name]); + return $this; + } + + /** * @param Property[] $props * @return static @@ -540,6 +597,9 @@ public function validate(): void if ($this->abstract && $this->final) { throw new Nette\InvalidStateException('Class cannot be abstract and final.'); + } elseif ($this->isEnum() && ($this->abstract || $this->final || $this->extends || $this->properties)) { + throw new Nette\InvalidStateException('Enum cannot be abstract or final or extends class or have properties.'); + } elseif (!$this->name && ($this->abstract || $this->final)) { throw new Nette\InvalidStateException('Anonymous class cannot be abstract or final.'); } @@ -559,6 +619,7 @@ private function validateNames(array $names): void public function __clone() { $clone = function ($item) { return clone $item; }; + $this->cases = array_map($clone, $this->cases); $this->consts = array_map($clone, $this->consts); $this->properties = array_map($clone, $this->properties); $this->methods = array_map($clone, $this->methods); diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index c9fa9261..6f1d8cfe 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -123,6 +123,9 @@ private function dumpObject(&$var, array $parents, int $level): string if ($var instanceof \Serializable) { return 'unserialize(' . $this->dumpString(serialize($var)) . ')'; + } elseif ($var instanceof \UnitEnum) { + return '\\' . get_class($var) . '::' . $var->name; + } elseif ($var instanceof \Closure) { throw new Nette\InvalidArgumentException('Cannot dump closure.'); } diff --git a/src/PhpGenerator/EnumCase.php b/src/PhpGenerator/EnumCase.php new file mode 100644 index 00000000..c3c497a1 --- /dev/null +++ b/src/PhpGenerator/EnumCase.php @@ -0,0 +1,41 @@ +value = $val; + return $this; + } + + + public function getValue() + { + return $this->value; + } +} diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 68cd3b97..e59a6b90 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -27,9 +27,17 @@ public function fromClassReflection(\ReflectionClass $from, bool $withBodies = f $class = $from->isAnonymous() ? new ClassType : new ClassType($from->getShortName(), new PhpNamespace($from->getNamespaceName())); - $class->setType($from->isInterface() ? $class::TYPE_INTERFACE : ($from->isTrait() ? $class::TYPE_TRAIT : $class::TYPE_CLASS)); - $class->setFinal($from->isFinal() && $class->isClass()); - $class->setAbstract($from->isAbstract() && $class->isClass()); + + if (PHP_VERSION_ID >= 80100 && $from->isEnum()) { + $class->setType($class::TYPE_ENUM); + $from = new \ReflectionEnum($from->getName()); + $enumIface = $from->isBacked() ? \BackedEnum::class : \UnitEnum::class; + } else { + $class->setType($from->isInterface() ? $class::TYPE_INTERFACE : ($from->isTrait() ? $class::TYPE_TRAIT : $class::TYPE_CLASS)); + $class->setFinal($from->isFinal() && $class->isClass()); + $class->setAbstract($from->isAbstract() && $class->isClass()); + $enumIface = null; + } $ifaces = $from->getInterfaceNames(); foreach ($ifaces as $iface) { @@ -40,6 +48,7 @@ public function fromClassReflection(\ReflectionClass $from, bool $withBodies = f if ($from->isInterface()) { $class->setExtends($ifaces); } else { + $ifaces = array_diff($ifaces, [$enumIface]); $class->setImplements($ifaces); } @@ -49,20 +58,25 @@ public function fromClassReflection(\ReflectionClass $from, bool $withBodies = f $class->setExtends($from->getParentClass()->name); $class->setImplements(array_diff($class->getImplements(), $from->getParentClass()->getInterfaceNames())); } - $props = $methods = $consts = []; + + $props = []; foreach ($from->getProperties() as $prop) { if ($prop->isDefault() && $prop->getDeclaringClass()->name === $from->name && (PHP_VERSION_ID < 80000 || !$prop->isPromoted()) + && !$class->isEnum() ) { $props[] = $this->fromPropertyReflection($prop); } } $class->setProperties($props); - $bodies = []; + $methods = $bodies = []; foreach ($from->getMethods() as $method) { - if ($method->getDeclaringClass()->name === $from->name) { + if ( + $method->getDeclaringClass()->name === $from->name + && (!$enumIface || !method_exists($enumIface, $method->name)) + ) { $methods[] = $m = $this->fromMethodReflection($method); if ($withBodies) { $srcMethod = Nette\Utils\Reflection::getMethodDeclaringMethod($method); @@ -76,12 +90,16 @@ public function fromClassReflection(\ReflectionClass $from, bool $withBodies = f } $class->setMethods($methods); + $consts = $cases = []; foreach ($from->getReflectionConstants() as $const) { - if ($const->getDeclaringClass()->name === $from->name) { + if ($class->isEnum() && $from->hasCase($const->name)) { + $cases[] = $this->fromCaseReflection($const); + } elseif ($const->getDeclaringClass()->name === $from->name) { $consts[] = $this->fromConstantReflection($const); } } $class->setConstants($consts); + $class->setCases($cases); return $class; } @@ -185,6 +203,16 @@ public function fromConstantReflection(\ReflectionClassConstant $from): Constant } + 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)); + return $const; + } + + public function fromPropertyReflection(\ReflectionProperty $from): Property { $defaults = $from->getDeclaringClass()->getDefaultProperties(); diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index 3310550e..cbec86cb 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -56,6 +56,14 @@ public function addTrait(string $name): ClassType } + public function addEnum(string $name): ClassType + { + return $this + ->addNamespace(Helpers::extractNamespace($name)) + ->addEnum(Helpers::extractShortName($name)); + } + + /** @param string|PhpNamespace $namespace */ public function addNamespace($namespace): PhpNamespace { diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 64ca1f4c..1ef00321 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -189,6 +189,12 @@ public function addTrait(string $name): ClassType } + public function addEnum(string $name): ClassType + { + return $this->addClass($name)->setType(ClassType::TYPE_ENUM); + } + + /** @return ClassType[] */ public function getClasses(): array { diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index e78c43cb..e6867375 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -128,6 +128,18 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st . ($resolutions ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n" : ";\n"); } + $cases = []; + foreach ($class->getCases() as $case) { + $cases[] = Helpers::formatDocComment((string) $case->getComment()) + . self::printAttributes($case->getAttributes(), $namespace) + . 'case ' . $case->getName() + . ($case->getValue() === null ? '' : ' = ' . $this->dump($case->getValue())) + . ";\n"; + } + $enumType = isset($case) && $case->getValue() !== null + ? $this->returnTypeColon . Type::getType($case->getValue()) + : ''; + $consts = []; foreach ($class->getConstants() as $const) { $def = ($const->getVisibility() ? $const->getVisibility() . ' ' : '') . 'const ' . $const->getName() . ' = '; @@ -158,6 +170,7 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st $members = array_filter([ implode('', $traits), + $this->joinProperties($cases), $this->joinProperties($consts), $this->joinProperties($properties), ($methods && $properties ? str_repeat("\n", $this->linesBetweenMethods - 1) : '') @@ -169,7 +182,7 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st . self::printAttributes($class->getAttributes(), $namespace) . ($class->isAbstract() ? 'abstract ' : '') . ($class->isFinal() ? 'final ' : '') - . ($class->getName() ? $class->getType() . ' ' . $class->getName() . ' ' : '') + . ($class->getName() ? $class->getType() . ' ' . $class->getName() . $enumType . ' ' : '') . ($class->getExtends() ? 'extends ' . implode(', ', array_map($resolver, (array) $class->getExtends())) . ' ' : '') . ($class->getImplements() ? 'implements ' . implode(', ', array_map($resolver, $class->getImplements())) . ' ' : '') . ($class->getName() ? "\n" : '') . "{\n" diff --git a/tests/PhpGenerator/ClassType.addMember.phpt b/tests/PhpGenerator/ClassType.addMember.phpt index e1a7905f..dab1611f 100644 --- a/tests/PhpGenerator/ClassType.addMember.phpt +++ b/tests/PhpGenerator/ClassType.addMember.phpt @@ -12,7 +12,7 @@ require __DIR__ . '/../bootstrap.php'; Assert::exception(function () { (new ClassType('Example')) ->addMember(new stdClass); -}, Nette\InvalidArgumentException::class, 'Argument must be Method|Property|Constant.'); +}, Nette\InvalidArgumentException::class, 'Argument must be Method|Property|Constant|EnumCase.'); $class = (new ClassType('Example')) diff --git a/tests/PhpGenerator/ClassType.enum.phpt b/tests/PhpGenerator/ClassType.enum.phpt new file mode 100644 index 00000000..a7a7cd57 --- /dev/null +++ b/tests/PhpGenerator/ClassType.enum.phpt @@ -0,0 +1,48 @@ +isEnum()); + +$enum + ->setTraits(['ObjectTrait']) + ->addComment("Description of class.\nThis is example\n") + ->addAttribute('ExampleAttribute') + ->addConstant('ACTIVE', false); + +$enum->addMethod('foo') + ->setBody('return 10;'); + +$enum->addCase('Clubs') + ->addComment('♣') + ->addAttribute('ValueAttribute'); +$enum->addCase('Diamonds') + ->addComment('♦'); +$enum->addCase('Hearts'); +$enum->addCase('Spades'); + +$res[] = $enum; + + +$enum = ClassType::enum('Method'); +$enum->addImplement('IOne'); + +$enum->addCase('GET', 'get'); +$enum->addCase('POST', 'post'); + +$res[] = $enum; + +sameFile(__DIR__ . '/expected/ClassType.enum.expect', implode("\n", $res)); diff --git a/tests/PhpGenerator/ClassType.from.enum.phpt b/tests/PhpGenerator/ClassType.from.enum.phpt new file mode 100644 index 00000000..d16e770e --- /dev/null +++ b/tests/PhpGenerator/ClassType.from.enum.phpt @@ -0,0 +1,18 @@ +dump(Suit::Clubs)); diff --git a/tests/PhpGenerator/PhpFile.phpt b/tests/PhpGenerator/PhpFile.phpt index a3a6e1bf..677afa18 100644 --- a/tests/PhpGenerator/PhpFile.phpt +++ b/tests/PhpGenerator/PhpFile.phpt @@ -48,6 +48,9 @@ Assert::same($interfaceC->getNamespace(), $namespaceBar); $traitD = $namespaceBar->addTrait('D'); Assert::same($traitD->getNamespace(), $namespaceBar); +$enumEN = $namespaceBar->addEnum('EN'); +Assert::same($enumEN->getNamespace(), $namespaceBar); + $classB ->addExtend('Foo\\A') ->addImplement('Foo\\B') diff --git a/tests/PhpGenerator/expected/ClassType.enum.expect b/tests/PhpGenerator/expected/ClassType.enum.expect new file mode 100644 index 00000000..022cbb54 --- /dev/null +++ b/tests/PhpGenerator/expected/ClassType.enum.expect @@ -0,0 +1,31 @@ +/** + * Description of class. + * This is example + */ +#[ExampleAttribute] +enum Suit +{ + use ObjectTrait; + + /** ♣ */ + #[ValueAttribute] + case Clubs; + + /** ♦ */ + case Diamonds; + case Hearts; + case Spades; + + const ACTIVE = false; + + public function foo() + { + return 10; + } +} + +enum Method: string implements IOne +{ + case GET = 'get'; + case POST = 'post'; +} diff --git a/tests/PhpGenerator/expected/ClassType.from.enum.expect b/tests/PhpGenerator/expected/ClassType.from.enum.expect new file mode 100644 index 00000000..61ef5732 --- /dev/null +++ b/tests/PhpGenerator/expected/ClassType.from.enum.expect @@ -0,0 +1,31 @@ +/** + * Description of enum. + */ +#[\ExampleAttribute] +enum Enum1 +{ + /** Commented */ + case Clubs; + + #[ExampleAttribute] + case Diamonds; + case Hearts; + case Spades; + + public const FOO = 123; + public const BAR = \Abc\Enum1::Clubs; + + public function foo($x = self::Diamonds) + { + } +} + +enum Enum2: string implements \Countable +{ + case GET = 'get'; + case POST = 'post'; + + public function count(): int + { + } +} diff --git a/tests/PhpGenerator/expected/PhpFile.bracketed.expect b/tests/PhpGenerator/expected/PhpFile.bracketed.expect index f2deb08b..12488830 100644 --- a/tests/PhpGenerator/expected/PhpFile.bracketed.expect +++ b/tests/PhpGenerator/expected/PhpFile.bracketed.expect @@ -37,6 +37,10 @@ namespace Bar trait D { } + + enum EN + { + } } diff --git a/tests/PhpGenerator/expected/PhpFile.regular.expect b/tests/PhpGenerator/expected/PhpFile.regular.expect index 02f85dbd..fc3455f8 100644 --- a/tests/PhpGenerator/expected/PhpFile.regular.expect +++ b/tests/PhpGenerator/expected/PhpFile.regular.expect @@ -37,6 +37,10 @@ trait D { } +enum EN +{ +} + namespace Baz; diff --git a/tests/PhpGenerator/fixtures/enum.php81 b/tests/PhpGenerator/fixtures/enum.php81 new file mode 100644 index 00000000..3128ca2e --- /dev/null +++ b/tests/PhpGenerator/fixtures/enum.php81 @@ -0,0 +1,37 @@ + Date: Tue, 24 Aug 2021 23:45:04 +0200 Subject: [PATCH 011/266] added support for intersection types foo&bar --- readme.md | 8 ++++---- src/PhpGenerator/Factory.php | 20 +++++++++++++++---- src/PhpGenerator/PhpNamespace.php | 4 ++-- src/PhpGenerator/Printer.php | 4 ++-- src/PhpGenerator/Type.php | 6 ++++++ tests/PhpGenerator/ClassType.from.81.phpt | 17 ++++++++++++++++ tests/PhpGenerator/PhpNamespace.phpt | 8 +++++--- .../expected/ClassType.from.81.expect | 9 +++++++++ tests/PhpGenerator/fixtures/classes.php81 | 13 ++++++++++++ 9 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 tests/PhpGenerator/ClassType.from.81.phpt create mode 100644 tests/PhpGenerator/expected/ClassType.from.81.expect create mode 100644 tests/PhpGenerator/fixtures/classes.php81 diff --git a/readme.md b/readme.md index 0de5e62f..bff81812 100644 --- a/readme.md +++ b/readme.md @@ -188,14 +188,14 @@ $class->addMember($methodRecount); Types ----- -Each type or union type can be passed as a string, you can also use predefined constants for native types: +Each type or union/intersection type can be passed as a string, you can also use predefined constants for native types: ```php use Nette\PhpGenerator\Type; -$member->setType('array'); -$member->setType(Type::ARRAY); -$member->setType('array|string'); +$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/Factory.php b/src/PhpGenerator/Factory.php index e59a6b90..867f127f 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -126,7 +126,10 @@ public function fromMethodReflection(\ReflectionMethod $from): Method if ($from->getReturnType() instanceof \ReflectionNamedType) { $method->setReturnType($from->getReturnType()->getName()); $method->setReturnNullable($from->getReturnType()->allowsNull()); - } elseif ($from->getReturnType() instanceof \ReflectionUnionType) { + } elseif ( + $from->getReturnType() instanceof \ReflectionUnionType + || $from->getReturnType() instanceof \ReflectionIntersectionType + ) { $method->setReturnType((string) $from->getReturnType()); } return $method; @@ -147,7 +150,10 @@ public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody if ($from->getReturnType() instanceof \ReflectionNamedType) { $function->setReturnType($from->getReturnType()->getName()); $function->setReturnNullable($from->getReturnType()->allowsNull()); - } elseif ($from->getReturnType() instanceof \ReflectionUnionType) { + } elseif ( + $from->getReturnType() instanceof \ReflectionUnionType + || $from->getReturnType() instanceof \ReflectionIntersectionType + ) { $function->setReturnType((string) $from->getReturnType()); } $function->setBody($withBody ? $this->loadFunctionBody($from) : ''); @@ -174,7 +180,10 @@ public function fromParameterReflection(\ReflectionParameter $from): Parameter if ($from->getType() instanceof \ReflectionNamedType) { $param->setType($from->getType()->getName()); $param->setNullable($from->getType()->allowsNull()); - } elseif ($from->getType() instanceof \ReflectionUnionType) { + } elseif ( + $from->getType() instanceof \ReflectionUnionType + || $from->getType() instanceof \ReflectionIntersectionType + ) { $param->setType((string) $from->getType()); } if ($from->isDefaultValueAvailable()) { @@ -228,7 +237,10 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property if ($from->getType() instanceof \ReflectionNamedType) { $prop->setType($from->getType()->getName()); $prop->setNullable($from->getType()->allowsNull()); - } elseif ($from->getType() instanceof \ReflectionUnionType) { + } elseif ( + $from->getType() instanceof \ReflectionUnionType + || $from->getType() instanceof \ReflectionIntersectionType + ) { $prop->setType((string) $from->getType()); } $prop->setInitialized($from->hasType() && array_key_exists($prop->getName(), $defaults)); diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 1ef00321..db31a788 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -126,9 +126,9 @@ public function getUses(): array } - public function unresolveUnionType(string $type): string + public function unresolveType(string $type): string { - return implode('|', array_map([$this, 'unresolveName'], explode('|', $type))); + return preg_replace_callback('~[^|&?]+~', function ($m) { return $this->unresolveName($m[0]); }, $type); } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index e6867375..8e6ef43e 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -119,7 +119,7 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st { $class->validate(); $resolver = $this->resolveTypes && $namespace - ? [$namespace, 'unresolveUnionType'] + ? [$namespace, 'unresolveType'] : function ($s) { return $s; }; $traits = []; @@ -310,7 +310,7 @@ public function printType(?string $type, bool $nullable = false, PhpNamespace $n return ''; } if ($this->resolveTypes && $namespace) { - $type = $namespace->unresolveUnionType($type); + $type = $namespace->unresolveType($type); } if ($nullable && strcasecmp($type, 'mixed')) { $type = strpos($type, '|') === false diff --git a/src/PhpGenerator/Type.php b/src/PhpGenerator/Type.php index 88e4b663..f7568550 100644 --- a/src/PhpGenerator/Type.php +++ b/src/PhpGenerator/Type.php @@ -46,6 +46,12 @@ public static function union(string ...$types): string } + public static function intersection(string ...$types): string + { + return implode('&', $types); + } + + public static function getType($value): ?string { if (is_object($value)) { diff --git a/tests/PhpGenerator/ClassType.from.81.phpt b/tests/PhpGenerator/ClassType.from.81.phpt new file mode 100644 index 00000000..c0379272 --- /dev/null +++ b/tests/PhpGenerator/ClassType.from.81.phpt @@ -0,0 +1,17 @@ +unresolveName('\A')); Assert::same('\A', $namespace->unresolveName('A')); Assert::same('A', $namespace->unresolveName('foo\A')); -Assert::same('A', $namespace->unresolveUnionType('foo\A')); -Assert::same('null|A', $namespace->unresolveUnionType('null|foo\A')); -Assert::same('', $namespace->unresolveUnionType('')); +Assert::same('A', $namespace->unresolveType('foo\A')); +Assert::same('null|A', $namespace->unresolveType('null|foo\A')); +Assert::same('?A', $namespace->unresolveType('?foo\A')); +Assert::same('A&\Countable', $namespace->unresolveType('foo\A&Countable')); +Assert::same('', $namespace->unresolveType('')); $namespace->addUse('Bar\C'); Assert::same(['C' => 'Bar\\C'], $namespace->getUses()); diff --git a/tests/PhpGenerator/expected/ClassType.from.81.expect b/tests/PhpGenerator/expected/ClassType.from.81.expect new file mode 100644 index 00000000..690d2b91 --- /dev/null +++ b/tests/PhpGenerator/expected/ClassType.from.81.expect @@ -0,0 +1,9 @@ +class Class11 +{ + public Foo&Bar $foo; + + + public function foo(Foo&Bar $c): Foo&Bar + { + } +} diff --git a/tests/PhpGenerator/fixtures/classes.php81 b/tests/PhpGenerator/fixtures/classes.php81 new file mode 100644 index 00000000..30e9be15 --- /dev/null +++ b/tests/PhpGenerator/fixtures/classes.php81 @@ -0,0 +1,13 @@ + Date: Wed, 25 Aug 2021 11:12:14 +0200 Subject: [PATCH 012/266] added support for readonly properties --- readme.md | 6 +++++- src/PhpGenerator/Factory.php | 1 + src/PhpGenerator/Printer.php | 10 ++++++++-- src/PhpGenerator/PromotedParameter.php | 17 +++++++++++++++++ src/PhpGenerator/Property.php | 17 +++++++++++++++++ tests/PhpGenerator/ClassType.phpt | 3 ++- tests/PhpGenerator/ClassType.promotion.phpt | 5 +++++ tests/PhpGenerator/expected/ClassType.expect | 2 +- .../expected/ClassType.from.81.expect | 1 + .../expected/ClassType.promotion.expect | 1 + tests/PhpGenerator/fixtures/classes.php81 | 2 ++ 11 files changed, 60 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index bff81812..a0554451 100644 --- a/readme.md +++ b/readme.md @@ -160,7 +160,11 @@ public function __construct( } ``` -If the property, constant, method or parameter already exist, it will be overwritten. +Readonly properties introduced by PHP 8.1 can be marked via `setReadOnly()`. + +------ + +If the added property, constant, method or parameter already exist, it will be overwritten. Members can be removed using `removeProperty()`, `removeConstant()`, `removeMethod()` or `removeParameter()`. diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 867f127f..53c32f9f 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -244,6 +244,7 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property $prop->setType((string) $from->getType()); } $prop->setInitialized($from->hasType() && array_key_exists($prop->getName(), $defaults)); + $prop->setReadOnly(PHP_VERSION_ID >= 80100 ? $from->isReadOnly() : false); } else { $prop->setInitialized(false); } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 8e6ef43e..47977fbc 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -152,7 +152,10 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st $properties = []; foreach ($class->getProperties() as $property) { $type = $property->getType(); - $def = (($property->getVisibility() ?: 'public') . ($property->isStatic() ? ' static' : '') . ' ' + $def = (($property->getVisibility() ?: 'public') + . ($property->isStatic() ? ' static' : '') + . ($property->isReadOnly() && $type ? ' readonly' : '') + . ' ' . ltrim($this->printType($type, $property->isNullable(), $namespace) . ' ') . '$' . $property->getName()); @@ -286,7 +289,10 @@ public function printParameters($function, PhpNamespace $namespace = null, int $ $params[] = ($promoted ? Helpers::formatDocComment((string) $promoted->getComment()) : '') . ($attrs = self::printAttributes($param->getAttributes(), $namespace, true)) - . ($promoted ? ($promoted->getVisibility() ?: 'public') . ' ' : '') + . ($promoted ? + ($promoted->getVisibility() ?: 'public') + . ($promoted->isReadOnly() && $type ? ' readonly' : '') + . ' ' : '') . ltrim($this->printType($type, $param->isNullable(), $namespace) . ' ') . ($param->isReference() ? '&' : '') . ($variadic ? '...' : '') diff --git a/src/PhpGenerator/PromotedParameter.php b/src/PhpGenerator/PromotedParameter.php index 00004831..e3316d09 100644 --- a/src/PhpGenerator/PromotedParameter.php +++ b/src/PhpGenerator/PromotedParameter.php @@ -17,4 +17,21 @@ final class PromotedParameter extends Parameter { use Traits\VisibilityAware; use Traits\CommentAware; + + /** @var bool */ + private $readOnly = false; + + + /** @return static */ + public function setReadOnly(bool $state = true): self + { + $this->readOnly = $state; + return $this; + } + + + public function isReadOnly(): bool + { + return $this->readOnly; + } } diff --git a/src/PhpGenerator/Property.php b/src/PhpGenerator/Property.php index 7d128c82..25751196 100644 --- a/src/PhpGenerator/Property.php +++ b/src/PhpGenerator/Property.php @@ -40,6 +40,9 @@ final class Property /** @var bool */ private $initialized = false; + /** @var bool */ + private $readOnly = false; + /** @return static */ public function setValue($val): self @@ -114,4 +117,18 @@ public function isInitialized(): bool { return $this->initialized || $this->value !== null; } + + + /** @return static */ + public function setReadOnly(bool $state = true): self + { + $this->readOnly = $state; + return $this; + } + + + public function isReadOnly(): bool + { + return $this->readOnly; + } } diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index c0605999..fd8f288a 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -58,7 +58,8 @@ $class->addProperty('order') ->setValue(new Literal('RecursiveIteratorIterator::SELF_FIRST')); $class->addProperty('typed1') - ->setType(Type::ARRAY); + ->setType(Type::ARRAY) + ->setReadOnly(); $class->addProperty('typed2') ->setType(Type::ARRAY) diff --git a/tests/PhpGenerator/ClassType.promotion.phpt b/tests/PhpGenerator/ClassType.promotion.phpt index be063b1e..fcff40f7 100644 --- a/tests/PhpGenerator/ClassType.promotion.phpt +++ b/tests/PhpGenerator/ClassType.promotion.phpt @@ -3,6 +3,7 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; +use Nette\PhpGenerator\Literal; require __DIR__ . '/../bootstrap.php'; @@ -18,4 +19,8 @@ $method->addPromotedParameter('c') ->addComment('promo') ->addAttribute('Example'); +$method->addPromotedParameter('d', new Literal('new Draft')) + ->setType('Draft') + ->setReadOnly(); + sameFile(__DIR__ . '/expected/ClassType.promotion.expect', (string) $class); diff --git a/tests/PhpGenerator/expected/ClassType.expect b/tests/PhpGenerator/expected/ClassType.expect index 5f837efa..595d8071 100644 --- a/tests/PhpGenerator/expected/ClassType.expect +++ b/tests/PhpGenerator/expected/ClassType.expect @@ -20,7 +20,7 @@ abstract class Example extends ParentClass implements IExample, IOne /** @var resource orignal file handle */ private $handle; public $order = RecursiveIteratorIterator::SELF_FIRST; - public array $typed1; + public readonly array $typed1; public ?array $typed2 = null; public array $typed3 = null; public static $sections = ['first' => true]; diff --git a/tests/PhpGenerator/expected/ClassType.from.81.expect b/tests/PhpGenerator/expected/ClassType.from.81.expect index 690d2b91..66207c1f 100644 --- a/tests/PhpGenerator/expected/ClassType.from.81.expect +++ b/tests/PhpGenerator/expected/ClassType.from.81.expect @@ -1,6 +1,7 @@ class Class11 { public Foo&Bar $foo; + public readonly array $ro; public function foo(Foo&Bar $c): Foo&Bar diff --git a/tests/PhpGenerator/expected/ClassType.promotion.expect b/tests/PhpGenerator/expected/ClassType.promotion.expect index b4a90616..d886b4d6 100644 --- a/tests/PhpGenerator/expected/ClassType.promotion.expect +++ b/tests/PhpGenerator/expected/ClassType.promotion.expect @@ -5,6 +5,7 @@ class Example public $b, /** promo */ #[Example] private string $c, + public readonly Draft $d = new Draft, ) { } } diff --git a/tests/PhpGenerator/fixtures/classes.php81 b/tests/PhpGenerator/fixtures/classes.php81 index 30e9be15..bcb062e7 100644 --- a/tests/PhpGenerator/fixtures/classes.php81 +++ b/tests/PhpGenerator/fixtures/classes.php81 @@ -8,6 +8,8 @@ class Class11 { public Foo&Bar $foo; + public readonly array $ro; + public function foo(Foo&Bar $c): Foo&Bar { } } From ed215d898b97dc66aec8ddf4607c2b5f0f195af8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 25 Aug 2021 11:22:28 +0200 Subject: [PATCH 013/266] added support for final constants --- readme.md | 5 +++-- src/PhpGenerator/Constant.php | 17 +++++++++++++++++ src/PhpGenerator/Factory.php | 1 + src/PhpGenerator/Printer.php | 5 ++++- tests/PhpGenerator/ClassType.phpt | 3 ++- tests/PhpGenerator/expected/ClassType.expect | 2 +- .../expected/ClassType.from.81.expect | 2 ++ tests/PhpGenerator/fixtures/classes.php81 | 2 ++ 8 files changed, 32 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index a0554451..55f2270f 100644 --- a/readme.md +++ b/readme.md @@ -87,7 +87,8 @@ We can add constants ([Constant](https://api.nette.org/3.0/Nette/PhpGenerator/Co ```php $class->addConstant('ID', 123) - ->setPrivate(); // constant visiblity + ->setProtected() // constant visiblity + ->setFinal(); $class->addProperty('items', [1, 2, 3]) ->setPrivate() // or setVisibility('private') @@ -103,7 +104,7 @@ $class->addProperty('list') It generates: ```php -private const ID = 123; +final protected const ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; diff --git a/src/PhpGenerator/Constant.php b/src/PhpGenerator/Constant.php index 4f046256..3c1de300 100644 --- a/src/PhpGenerator/Constant.php +++ b/src/PhpGenerator/Constant.php @@ -26,6 +26,9 @@ final class Constant /** @var mixed */ private $value; + /** @var bool */ + private $final = false; + /** @return static */ public function setValue($val): self @@ -39,4 +42,18 @@ public function getValue() { return $this->value; } + + + /** @return static */ + public function setFinal(bool $state = true): self + { + $this->final = $state; + return $this; + } + + + public function isFinal(): bool + { + return $this->final; + } } diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 53c32f9f..4f4d04a1 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -206,6 +206,7 @@ public function fromConstantReflection(\ReflectionClassConstant $from): Constant ? ClassType::VISIBILITY_PRIVATE : ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ClassType::VISIBILITY_PUBLIC) ); + $const->setFinal(PHP_VERSION_ID >= 80100 ? $from->isFinal() : false); $const->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); $const->setAttributes(self::getAttributes($from)); return $const; diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 47977fbc..f3abc741 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -142,7 +142,10 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st $consts = []; foreach ($class->getConstants() as $const) { - $def = ($const->getVisibility() ? $const->getVisibility() . ' ' : '') . 'const ' . $const->getName() . ' = '; + $def = ($const->isFinal() ? 'final ' : '') + . ($const->getVisibility() ? $const->getVisibility() . ' ' : '') + . 'const ' . $const->getName() . ' = '; + $consts[] = Helpers::formatDocComment((string) $const->getComment()) . self::printAttributes($const->getAttributes(), $namespace) . $def diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index fd8f288a..4ed36c42 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -36,7 +36,8 @@ $class ->addComment("Description of class.\nThis is example\n") ->addComment('@property-read Nette\Forms\Form $form') ->setConstants(['ROLE' => 'admin']) - ->addConstant('ACTIVE', false); + ->addConstant('ACTIVE', false) + ->setFinal(); Assert::false($class->isFinal()); Assert::true($class->isAbstract()); diff --git a/tests/PhpGenerator/expected/ClassType.expect b/tests/PhpGenerator/expected/ClassType.expect index 595d8071..758aaf19 100644 --- a/tests/PhpGenerator/expected/ClassType.expect +++ b/tests/PhpGenerator/expected/ClassType.expect @@ -12,7 +12,7 @@ abstract class Example extends ParentClass implements IExample, IOne } const ROLE = 'admin'; - const ACTIVE = false; + final const ACTIVE = false; /** Commented */ private const FORCE_ARRAY = Nette\Utils\Json::FORCE_ARRAY; diff --git a/tests/PhpGenerator/expected/ClassType.from.81.expect b/tests/PhpGenerator/expected/ClassType.from.81.expect index 66207c1f..0c044c35 100644 --- a/tests/PhpGenerator/expected/ClassType.from.81.expect +++ b/tests/PhpGenerator/expected/ClassType.from.81.expect @@ -1,5 +1,7 @@ class Class11 { + final public const FOO = 10; + public Foo&Bar $foo; public readonly array $ro; diff --git a/tests/PhpGenerator/fixtures/classes.php81 b/tests/PhpGenerator/fixtures/classes.php81 index bcb062e7..f927f27f 100644 --- a/tests/PhpGenerator/fixtures/classes.php81 +++ b/tests/PhpGenerator/fixtures/classes.php81 @@ -6,6 +6,8 @@ namespace Abc; class Class11 { + final public const FOO = 10; + public Foo&Bar $foo; public readonly array $ro; From 4e7be9e2d2d00e8241fa217fc90444e51b66d81b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 25 Aug 2021 12:00:47 +0200 Subject: [PATCH 014/266] Dumper: can dump static Closure --- src/PhpGenerator/Dumper.php | 4 ++++ tests/PhpGenerator/Dumper.dump().phpt | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 6f1d8cfe..52c10dd8 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -127,6 +127,10 @@ private function dumpObject(&$var, array $parents, int $level): string return '\\' . get_class($var) . '::' . $var->name; } elseif ($var instanceof \Closure) { + $inner = Nette\Utils\Callback::unwrap($var); + if (Nette\Utils\Callback::isStatic($inner)) { + return '\Closure::fromCallable(' . $this->dump($inner) . ')'; + } throw new Nette\InvalidArgumentException('Cannot dump closure.'); } diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index 3106527b..ec80cc71 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -127,6 +127,16 @@ Assert::exception(function () { // closures +Assert::same( + "\\Closure::fromCallable('strlen')", + $dumper->dump(Closure::fromCallable('strlen')) +); + +Assert::same( + "\\Closure::fromCallable(['Nette\\PhpGenerator\\ClassType', 'from'])", + $dumper->dump(Closure::fromCallable([Nette\PhpGenerator\ClassType::class, 'from'])) +); + Assert::exception(function () { $dumper = new Dumper; $dumper->dump(function () {}); From 2f28a34203ea2e730371d913fc06ae45b6e9baaa Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 25 Aug 2021 12:20:07 +0200 Subject: [PATCH 015/266] Literal: accepts parameters --- readme.md | 11 ++++++++++- src/PhpGenerator/Literal.php | 6 ++++-- tests/PhpGenerator/ClassType.promotion.phpt | 2 +- tests/PhpGenerator/Dumper.dump().phpt | 1 + .../PhpGenerator/expected/ClassType.promotion.expect | 2 +- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index 55f2270f..79dd8948 100644 --- a/readme.md +++ b/readme.md @@ -272,7 +272,7 @@ It is possible to add a comment or [attributes](#attributes) to each case using Literals -------- -You can pass any PHP code to property or parameter default values via `Literal`: +With `Literal` you can pass arbitrary PHP code to, for example, default property or parameter values etc: ```php use Nette\PhpGenerator\Literal; @@ -300,6 +300,15 @@ class Demo } ``` +You can also pass parameters to `Literal` and have it formatted into valid PHP code using [special placeholders](#method-and-function-body-generator): + +```php +new Literal('substr(?, ?)', [$a, $b]); +// generates, for example: substr('hello', 5); +``` + + + Using Traits ------------ diff --git a/src/PhpGenerator/Literal.php b/src/PhpGenerator/Literal.php index 48dbd83e..5e66987a 100644 --- a/src/PhpGenerator/Literal.php +++ b/src/PhpGenerator/Literal.php @@ -19,9 +19,11 @@ class Literal private $value; - public function __construct(string $value) + public function __construct(string $value, array $args = null) { - $this->value = $value; + $this->value = $args === null + ? $value + : (new Dumper)->format($value, ...$args); } diff --git a/tests/PhpGenerator/ClassType.promotion.phpt b/tests/PhpGenerator/ClassType.promotion.phpt index fcff40f7..a76f4045 100644 --- a/tests/PhpGenerator/ClassType.promotion.phpt +++ b/tests/PhpGenerator/ClassType.promotion.phpt @@ -19,7 +19,7 @@ $method->addPromotedParameter('c') ->addComment('promo') ->addAttribute('Example'); -$method->addPromotedParameter('d', new Literal('new Draft')) +$method->addPromotedParameter('d', new Literal('new Draft(?)', [10])) ->setType('Draft') ->setReadOnly(); diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index ec80cc71..be3ee5e3 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -44,6 +44,7 @@ Assert::same('\'He\ll\\\\\o \\\'wor\\\\\\\'ld\\\\\'', $dumper->dump('He\ll\\\o \ // literal Assert::same('[$s]', $dumper->dump([new Literal('$s')])); +Assert::same("[strlen('hello')]", $dumper->dump([new Literal('strlen(?)', ['hello'])])); same('[ function () { return 1; diff --git a/tests/PhpGenerator/expected/ClassType.promotion.expect b/tests/PhpGenerator/expected/ClassType.promotion.expect index d886b4d6..81dd351e 100644 --- a/tests/PhpGenerator/expected/ClassType.promotion.expect +++ b/tests/PhpGenerator/expected/ClassType.promotion.expect @@ -5,7 +5,7 @@ class Example public $b, /** promo */ #[Example] private string $c, - public readonly Draft $d = new Draft, + public readonly Draft $d = new Draft(10), ) { } } From b35f4068877a58bd26b59a2c3841989009b7d484 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 14 Sep 2021 14:27:58 +0200 Subject: [PATCH 016/266] Factory: refactoring --- src/PhpGenerator/Factory.php | 50 ++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 4f4d04a1..8972add4 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -111,11 +111,7 @@ public function fromMethodReflection(\ReflectionMethod $from): Method $method->setParameters(array_map([$this, 'fromParameterReflection'], $from->getParameters())); $method->setStatic($from->isStatic()); $isInterface = $from->getDeclaringClass()->isInterface(); - $method->setVisibility( - $from->isPrivate() - ? ClassType::VISIBILITY_PRIVATE - : ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ($isInterface ? null : ClassType::VISIBILITY_PUBLIC)) - ); + $method->setVisibility($isInterface ? null : $this->getVisibility($from)); $method->setFinal($from->isFinal()); $method->setAbstract($from->isAbstract() && !$isInterface); $method->setBody($from->isAbstract() ? null : ''); @@ -201,11 +197,7 @@ public function fromConstantReflection(\ReflectionClassConstant $from): Constant { $const = new Constant($from->name); $const->setValue($from->getValue()); - $const->setVisibility( - $from->isPrivate() - ? ClassType::VISIBILITY_PRIVATE - : ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ClassType::VISIBILITY_PUBLIC) - ); + $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)); @@ -229,11 +221,7 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property $prop = new Property($from->name); $prop->setValue($defaults[$prop->getName()] ?? null); $prop->setStatic($from->isStatic()); - $prop->setVisibility( - $from->isPrivate() - ? ClassType::VISIBILITY_PRIVATE - : ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ClassType::VISIBILITY_PUBLIC) - ); + $prop->setVisibility($this->getVisibility($from)); if (PHP_VERSION_ID >= 70400) { if ($from->getType() instanceof \ReflectionNamedType) { $prop->setType($from->getType()->getName()); @@ -255,6 +243,25 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property } + private function getAttributes($from): array + { + if (PHP_VERSION_ID < 80000) { + return []; + } + return array_map(function ($attr) { + return new Attribute($attr->getName(), $attr->getArguments()); + }, $from->getAttributes()); + } + + + private function getVisibility($from): string + { + return $from->isPrivate() + ? ClassType::VISIBILITY_PRIVATE + : ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ClassType::VISIBILITY_PUBLIC); + } + + private function loadMethodBodies(\ReflectionClass $from): array { if ($from->isAnonymous()) { @@ -395,17 +402,4 @@ private function parse($from): array return [$code, $stmts]; } - - - private function getAttributes($from): array - { - if (PHP_VERSION_ID < 80000) { - return []; - } - $res = []; - foreach ($from->getAttributes() as $attr) { - $res[] = new Attribute($attr->getName(), $attr->getArguments()); - } - return $res; - } } From 249f3077f36a9c52740d1893cae812592cff6ae5 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 15 Sep 2021 19:08:15 +0200 Subject: [PATCH 017/266] Factory: refactoring, moved parser to Extractor --- src/PhpGenerator/Extractor.php | 160 ++++++++++++++++++ src/PhpGenerator/Factory.php | 156 ++--------------- tests/PhpGenerator/ClassType.from.bodies.phpt | 2 +- .../Extractor.getFunctionBody.phpt | 30 ++++ .../Extractor.getMethodBodies.phpt | 47 +++++ 5 files changed, 253 insertions(+), 142 deletions(-) create mode 100644 src/PhpGenerator/Extractor.php create mode 100644 tests/PhpGenerator/Extractor.getFunctionBody.phpt create mode 100644 tests/PhpGenerator/Extractor.getMethodBodies.phpt diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php new file mode 100644 index 00000000..3f53195a --- /dev/null +++ b/src/PhpGenerator/Extractor.php @@ -0,0 +1,160 @@ +parseCode($code); + } + + + private function parseCode(string $code): void + { + $this->code = str_replace("\r\n", "\n", $code); + $lexer = new PhpParser\Lexer(['usedAttributes' => ['startFilePos', 'endFilePos']]); + $parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7, $lexer); + $stmts = $parser->parse($this->code); + + $traverser = new PhpParser\NodeTraverser; + $traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver(null, ['replaceNodes' => false])); + $this->statements = $traverser->traverse($stmts); + } + + + public function extractMethodBodies(string $className): array + { + $nodeFinder = new NodeFinder; + $classNode = $nodeFinder->findFirst($this->statements, function (Node $node) use ($className) { + return ($node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Trait_) + && $node->namespacedName->toString() === $className; + }); + + $res = []; + foreach ($nodeFinder->findInstanceOf($classNode, Node\Stmt\ClassMethod::class) as $methodNode) { + /** @var Node\Stmt\ClassMethod $methodNode */ + if ($methodNode->stmts) { + $body = $this->extractBody($nodeFinder, $methodNode->stmts); + $res[$methodNode->name->toString()] = Helpers::unindent($body, 2); + } + } + return $res; + } + + + public function extractFunctionBody(string $name): ?string + { + $nodeFinder = new NodeFinder; + /** @var Node\Stmt\Function_ $functionNode */ + $functionNode = $nodeFinder->findFirst($this->statements, function (Node $node) use ($name) { + return $node instanceof Node\Stmt\Function_ && $node->namespacedName->toString() === $name; + }); + + $body = $this->extractBody($nodeFinder, $functionNode->stmts); + return Helpers::unindent($body, 1); + } + + + /** + * @param Node[] $statements + */ + private function extractBody(NodeFinder $nodeFinder, array $statements): string + { + $start = $statements[0]->getAttribute('startFilePos'); + $body = substr($this->code, $start, end($statements)->getAttribute('endFilePos') - $start + 1); + + $replacements = []; + // name-nodes => resolved fully-qualified name + foreach ($nodeFinder->findInstanceOf($statements, Node\Name::class) as $node) { + if ($node->hasAttribute('resolvedName') + && $node->getAttribute('resolvedName') instanceof Node\Name\FullyQualified + ) { + $replacements[] = [ + $node->getStartFilePos(), + $node->getEndFilePos(), + $node->getAttribute('resolvedName')->toCodeString(), + ]; + } + } + + // multi-line strings => singleline + foreach (array_merge( + $nodeFinder->findInstanceOf($statements, Node\Scalar\String_::class), + $nodeFinder->findInstanceOf($statements, Node\Scalar\EncapsedStringPart::class) + ) as $node) { + /** @var Node\Scalar\String_|Node\Scalar\EncapsedStringPart $node */ + $token = substr($body, $node->getStartFilePos() - $start, $node->getEndFilePos() - $node->getStartFilePos() + 1); + if (strpos($token, "\n") !== false) { + $quote = $node instanceof Node\Scalar\String_ ? '"' : ''; + $replacements[] = [ + $node->getStartFilePos(), + $node->getEndFilePos(), + $quote . addcslashes($node->value, "\x00..\x1F") . $quote, + ]; + } + } + + // HEREDOC => "string" + foreach ($nodeFinder->findInstanceOf($statements, Node\Scalar\Encapsed::class) as $node) { + /** @var Node\Scalar\Encapsed $node */ + if ($node->getAttribute('kind') === Node\Scalar\String_::KIND_HEREDOC) { + $replacements[] = [ + $node->getStartFilePos(), + $node->parts[0]->getStartFilePos() - 1, + '"', + ]; + $replacements[] = [ + end($node->parts)->getEndFilePos() + 1, + $node->getEndFilePos(), + '"', + ]; + } + } + + //sort collected resolved names by position in file + usort($replacements, function ($a, $b) { + return $a[0] <=> $b[0]; + }); + $correctiveOffset = -$start; + //replace changes body length so we need correct offset + foreach ($replacements as [$startPos, $endPos, $replacement]) { + $replacingStringLength = $endPos - $startPos + 1; + $body = substr_replace( + $body, + $replacement, + $correctiveOffset + $startPos, + $replacingStringLength + ); + $correctiveOffset += strlen($replacement) - $replacingStringLength; + } + return $body; + } +} diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 8972add4..3b0d9db4 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -10,9 +10,6 @@ namespace Nette\PhpGenerator; use Nette; -use PhpParser; -use PhpParser\Node; -use PhpParser\ParserFactory; /** @@ -24,6 +21,10 @@ final class Factory public function fromClassReflection(\ReflectionClass $from, bool $withBodies = false): ClassType { + if ($withBodies && $from->isAnonymous()) { + throw new Nette\NotSupportedException('The $withBodies parameter cannot be used for anonymous functions.'); + } + $class = $from->isAnonymous() ? new ClassType : new ClassType($from->getShortName(), new PhpNamespace($from->getNamespaceName())); @@ -80,8 +81,8 @@ public function fromClassReflection(\ReflectionClass $from, bool $withBodies = f $methods[] = $m = $this->fromMethodReflection($method); if ($withBodies) { $srcMethod = Nette\Utils\Reflection::getMethodDeclaringMethod($method); - $srcClass = $srcMethod->getDeclaringClass()->name; - $b = $bodies[$srcClass] = $bodies[$srcClass] ?? $this->loadMethodBodies($srcMethod->getDeclaringClass()); + $srcClass = $srcMethod->getDeclaringClass(); + $b = $bodies[$srcClass->name] = $bodies[$srcClass->name] ?? $this->getExtractor($srcClass)->extractMethodBodies($srcClass->name); if (isset($b[$srcMethod->name])) { $m->setBody($b[$srcMethod->name]); } @@ -152,7 +153,12 @@ public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody ) { $function->setReturnType((string) $from->getReturnType()); } - $function->setBody($withBody ? $this->loadFunctionBody($from) : ''); + if ($withBody) { + if ($from->isClosure()) { + throw new Nette\NotSupportedException('The $withBody parameter cannot be used for closures.'); + } + $function->setBody($this->getExtractor($from)->extractFunctionBody($from->name)); + } return $function; } @@ -262,144 +268,12 @@ private function getVisibility($from): string } - private function loadMethodBodies(\ReflectionClass $from): array - { - if ($from->isAnonymous()) { - throw new Nette\NotSupportedException('Anonymous classes are not supported.'); - } - - [$code, $stmts] = $this->parse($from); - $nodeFinder = new PhpParser\NodeFinder; - $class = $nodeFinder->findFirst($stmts, function (Node $node) use ($from) { - return ($node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Trait_) && $node->namespacedName->toString() === $from->name; - }); - - $bodies = []; - foreach ($nodeFinder->findInstanceOf($class, Node\Stmt\ClassMethod::class) as $method) { - /** @var Node\Stmt\ClassMethod $method */ - if ($method->stmts) { - $body = $this->extractBody($nodeFinder, $code, $method->stmts); - $bodies[$method->name->toString()] = Helpers::unindent($body, 2); - } - } - return $bodies; - } - - - private function loadFunctionBody(\ReflectionFunction $from): string - { - if ($from->isClosure()) { - throw new Nette\NotSupportedException('Closures are not supported.'); - } - - [$code, $stmts] = $this->parse($from); - - $nodeFinder = new PhpParser\NodeFinder; - /** @var Node\Stmt\Function_ $function */ - $function = $nodeFinder->findFirst($stmts, function (Node $node) use ($from) { - return $node instanceof Node\Stmt\Function_ && $node->namespacedName->toString() === $from->name; - }); - - $body = $this->extractBody($nodeFinder, $code, $function->stmts); - return Helpers::unindent($body, 1); - } - - - /** - * @param Node[] $statements - */ - private function extractBody(PhpParser\NodeFinder $nodeFinder, string $originalCode, array $statements): string - { - $start = $statements[0]->getAttribute('startFilePos'); - $body = substr($originalCode, $start, end($statements)->getAttribute('endFilePos') - $start + 1); - - $replacements = []; - // name-nodes => resolved fully-qualified name - foreach ($nodeFinder->findInstanceOf($statements, Node\Name::class) as $node) { - if ($node->hasAttribute('resolvedName') - && $node->getAttribute('resolvedName') instanceof Node\Name\FullyQualified - ) { - $replacements[] = [ - $node->getStartFilePos(), - $node->getEndFilePos(), - $node->getAttribute('resolvedName')->toCodeString(), - ]; - } - } - - // multi-line strings => singleline - foreach (array_merge( - $nodeFinder->findInstanceOf($statements, Node\Scalar\String_::class), - $nodeFinder->findInstanceOf($statements, Node\Scalar\EncapsedStringPart::class) - ) as $node) { - /** @var Node\Scalar\String_|Node\Scalar\EncapsedStringPart $node */ - $token = substr($body, $node->getStartFilePos() - $start, $node->getEndFilePos() - $node->getStartFilePos() + 1); - if (strpos($token, "\n") !== false) { - $quote = $node instanceof Node\Scalar\String_ ? '"' : ''; - $replacements[] = [ - $node->getStartFilePos(), - $node->getEndFilePos(), - $quote . addcslashes($node->value, "\x00..\x1F") . $quote, - ]; - } - } - - // HEREDOC => "string" - foreach ($nodeFinder->findInstanceOf($statements, Node\Scalar\Encapsed::class) as $node) { - /** @var Node\Scalar\Encapsed $node */ - if ($node->getAttribute('kind') === Node\Scalar\String_::KIND_HEREDOC) { - $replacements[] = [ - $node->getStartFilePos(), - $node->parts[0]->getStartFilePos() - 1, - '"', - ]; - $replacements[] = [ - end($node->parts)->getEndFilePos() + 1, - $node->getEndFilePos(), - '"', - ]; - } - } - - //sort collected resolved names by position in file - usort($replacements, function ($a, $b) { - return $a[0] <=> $b[0]; - }); - $correctiveOffset = -$start; - //replace changes body length so we need correct offset - foreach ($replacements as [$startPos, $endPos, $replacement]) { - $replacingStringLength = $endPos - $startPos + 1; - $body = substr_replace( - $body, - $replacement, - $correctiveOffset + $startPos, - $replacingStringLength - ); - $correctiveOffset += strlen($replacement) - $replacingStringLength; - } - return $body; - } - - - private function parse($from): array + private function getExtractor($from): Extractor { $file = $from->getFileName(); - if (!class_exists(ParserFactory::class)) { - throw new Nette\NotSupportedException("PHP-Parser is required to load method bodies, install package 'nikic/php-parser'."); - } elseif (!$file) { + if (!$file) { throw new Nette\InvalidStateException("Source code of $from->name not found."); } - - $lexer = new PhpParser\Lexer(['usedAttributes' => ['startFilePos', 'endFilePos']]); - $parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7, $lexer); - $code = file_get_contents($file); - $code = str_replace("\r\n", "\n", $code); - $stmts = $parser->parse($code); - - $traverser = new PhpParser\NodeTraverser; - $traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver(null, ['replaceNodes' => false])); - $stmts = $traverser->traverse($stmts); - - return [$code, $stmts]; + return new Extractor(file_get_contents($file)); } } diff --git a/tests/PhpGenerator/ClassType.from.bodies.phpt b/tests/PhpGenerator/ClassType.from.bodies.phpt index 100e270e..c080e2ea 100644 --- a/tests/PhpGenerator/ClassType.from.bodies.phpt +++ b/tests/PhpGenerator/ClassType.from.bodies.phpt @@ -21,7 +21,7 @@ Assert::exception(function () { { } }); -}, Nette\NotSupportedException::class, 'Anonymous classes are not supported.'); +}, Nette\NotSupportedException::class, 'The $withBodies parameter cannot be used for anonymous functions.'); $res = ClassType::withBodiesFrom(Abc\Class7::class); diff --git a/tests/PhpGenerator/Extractor.getFunctionBody.phpt b/tests/PhpGenerator/Extractor.getFunctionBody.phpt new file mode 100644 index 00000000..304860d5 --- /dev/null +++ b/tests/PhpGenerator/Extractor.getFunctionBody.phpt @@ -0,0 +1,30 @@ +extractFunctionBody('NS\bar1') +); diff --git a/tests/PhpGenerator/Extractor.getMethodBodies.phpt b/tests/PhpGenerator/Extractor.getMethodBodies.phpt new file mode 100644 index 00000000..1ebf3d6c --- /dev/null +++ b/tests/PhpGenerator/Extractor.getMethodBodies.phpt @@ -0,0 +1,47 @@ +extractMethodBodies('NS\Undefined'); +Assert::same([], $bodies); + +$bodies = $extractor->extractMethodBodies('NS\Foo'); +Assert::same([ + 'bar1' => "\$a = 10;\necho 123;", + 'bar2' => 'echo "hello";', +], $bodies); From a062c558f8eeb37107949d9dc2bc10f5eaafc8e2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 15 Sep 2021 20:24:08 +0200 Subject: [PATCH 018/266] Extractor: refactoring --- src/PhpGenerator/Extractor.php | 79 ++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 3f53195a..be57b6ca 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -62,8 +62,7 @@ public function extractMethodBodies(string $className): array foreach ($nodeFinder->findInstanceOf($classNode, Node\Stmt\ClassMethod::class) as $methodNode) { /** @var Node\Stmt\ClassMethod $methodNode */ if ($methodNode->stmts) { - $body = $this->extractBody($nodeFinder, $methodNode->stmts); - $res[$methodNode->name->toString()] = Helpers::unindent($body, 2); + $res[$methodNode->name->toString()] = $this->getReformattedBody($methodNode->stmts, 2); } } return $res; @@ -72,34 +71,38 @@ public function extractMethodBodies(string $className): array public function extractFunctionBody(string $name): ?string { - $nodeFinder = new NodeFinder; - /** @var Node\Stmt\Function_ $functionNode */ - $functionNode = $nodeFinder->findFirst($this->statements, function (Node $node) use ($name) { + $functionNode = (new NodeFinder)->findFirst($this->statements, function (Node $node) use ($name) { return $node instanceof Node\Stmt\Function_ && $node->namespacedName->toString() === $name; }); - $body = $this->extractBody($nodeFinder, $functionNode->stmts); - return Helpers::unindent($body, 1); + return $this->getReformattedBody($functionNode->stmts, 1); } - /** - * @param Node[] $statements - */ - private function extractBody(NodeFinder $nodeFinder, array $statements): string + /** @param Node[] $statements */ + private function getReformattedBody(array $statements, int $level): string { - $start = $statements[0]->getAttribute('startFilePos'); - $body = substr($this->code, $start, end($statements)->getAttribute('endFilePos') - $start + 1); + $replacements = $this->prepareReplacements($statements); + $body = $this->getNodeContents(...$statements); + $body = $this->performReplacements($body, $replacements); + return Helpers::unindent($body, $level); + } + + private function prepareReplacements(array $statements): array + { + $start = $statements[0]->getStartFilePos(); $replacements = []; + $nodeFinder = new NodeFinder; + // name-nodes => resolved fully-qualified name foreach ($nodeFinder->findInstanceOf($statements, Node\Name::class) as $node) { if ($node->hasAttribute('resolvedName') && $node->getAttribute('resolvedName') instanceof Node\Name\FullyQualified ) { $replacements[] = [ - $node->getStartFilePos(), - $node->getEndFilePos(), + $node->getStartFilePos() - $start, + $node->getEndFilePos() - $start, $node->getAttribute('resolvedName')->toCodeString(), ]; } @@ -111,12 +114,12 @@ private function extractBody(NodeFinder $nodeFinder, array $statements): string $nodeFinder->findInstanceOf($statements, Node\Scalar\EncapsedStringPart::class) ) as $node) { /** @var Node\Scalar\String_|Node\Scalar\EncapsedStringPart $node */ - $token = substr($body, $node->getStartFilePos() - $start, $node->getEndFilePos() - $node->getStartFilePos() + 1); + $token = $this->getNodeContents($node); if (strpos($token, "\n") !== false) { $quote = $node instanceof Node\Scalar\String_ ? '"' : ''; $replacements[] = [ - $node->getStartFilePos(), - $node->getEndFilePos(), + $node->getStartFilePos() - $start, + $node->getEndFilePos() - $start, $quote . addcslashes($node->value, "\x00..\x1F") . $quote, ]; } @@ -127,34 +130,46 @@ private function extractBody(NodeFinder $nodeFinder, array $statements): string /** @var Node\Scalar\Encapsed $node */ if ($node->getAttribute('kind') === Node\Scalar\String_::KIND_HEREDOC) { $replacements[] = [ - $node->getStartFilePos(), - $node->parts[0]->getStartFilePos() - 1, + $node->getStartFilePos() - $start, + $node->parts[0]->getStartFilePos() - $start - 1, '"', ]; $replacements[] = [ - end($node->parts)->getEndFilePos() + 1, - $node->getEndFilePos(), + end($node->parts)->getEndFilePos() - $start + 1, + $node->getEndFilePos() - $start, '"', ]; } } - //sort collected resolved names by position in file - usort($replacements, function ($a, $b) { + return $replacements; + } + + + private function performReplacements(string $s, array $replacements): string + { + usort($replacements, function ($a, $b) { // sort by position in file return $a[0] <=> $b[0]; }); - $correctiveOffset = -$start; - //replace changes body length so we need correct offset - foreach ($replacements as [$startPos, $endPos, $replacement]) { - $replacingStringLength = $endPos - $startPos + 1; - $body = substr_replace( - $body, + + $correctiveOffset = 0; + foreach ($replacements as [$start, $end, $replacement]) { + $replacingStringLength = $end - $start + 1; + $s = substr_replace( + $s, $replacement, - $correctiveOffset + $startPos, + $correctiveOffset + $start, $replacingStringLength ); $correctiveOffset += strlen($replacement) - $replacingStringLength; } - return $body; + return $s; + } + + + private function getNodeContents(Node ...$nodes): string + { + $start = $nodes[0]->getStartFilePos(); + return substr($this->code, $start, end($nodes)->getEndFilePos() - $start + 1); } } From 5cc43eef145e5b5aa20f3c58f80287a22d068a1c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 17 Sep 2021 17:11:34 +0200 Subject: [PATCH 019/266] Factory: parsed source code is cached --- src/PhpGenerator/Factory.php | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 3b0d9db4..6b58d5b8 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -19,6 +19,10 @@ final class Factory { use Nette\SmartObject; + private $bodyCache = []; + private $extractorCache = []; + + public function fromClassReflection(\ReflectionClass $from, bool $withBodies = false): ClassType { if ($withBodies && $from->isAnonymous()) { @@ -72,7 +76,7 @@ public function fromClassReflection(\ReflectionClass $from, bool $withBodies = f } $class->setProperties($props); - $methods = $bodies = []; + $methods = []; foreach ($from->getMethods() as $method) { if ( $method->getDeclaringClass()->name === $from->name @@ -82,9 +86,10 @@ public function fromClassReflection(\ReflectionClass $from, bool $withBodies = f if ($withBodies) { $srcMethod = Nette\Utils\Reflection::getMethodDeclaringMethod($method); $srcClass = $srcMethod->getDeclaringClass(); - $b = $bodies[$srcClass->name] = $bodies[$srcClass->name] ?? $this->getExtractor($srcClass)->extractMethodBodies($srcClass->name); - if (isset($b[$srcMethod->name])) { - $m->setBody($b[$srcMethod->name]); + $bodies = &$this->bodyCache[$srcClass->name]; + $bodies = $bodies ?? $this->getExtractor($srcClass)->extractMethodBodies($srcClass->name); + if (isset($bodies[$srcMethod->name])) { + $m->setBody($bodies[$srcMethod->name]); } } } @@ -271,7 +276,10 @@ private function getVisibility($from): string private function getExtractor($from): Extractor { $file = $from->getFileName(); - if (!$file) { + $cache = &$this->extractorCache[$file]; + if ($cache !== null) { + return $cache; + } elseif (!$file) { throw new Nette\InvalidStateException("Source code of $from->name not found."); } return new Extractor(file_get_contents($file)); From c4986f6a47f621f4f50dd265e6ed7e0862b07127 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 15 Sep 2021 13:47:17 +0200 Subject: [PATCH 020/266] tests: improvements --- .github/workflows/coding-style.yml | 2 +- tests/PhpGenerator/ClassType.from.74.phpt | 2 +- tests/PhpGenerator/ClassType.from.80.phpt | 2 +- tests/PhpGenerator/ClassType.from.81.phpt | 2 +- tests/PhpGenerator/ClassType.from.bodies.phpt | 2 +- tests/PhpGenerator/ClassType.from.enum.phpt | 2 +- tests/PhpGenerator/ClassType.from.phpt | 2 +- tests/PhpGenerator/ClassType.from.trait.phpt | 38 +++++++++---------- tests/PhpGenerator/ClassType.inheritance.phpt | 2 +- tests/PhpGenerator/Method.returnTypes.phpt | 4 +- .../PhpGenerator/Method.scalarParameters.phpt | 8 ++-- tests/PhpGenerator/Method.variadics.phpt | 4 +- tests/PhpGenerator/PhpFile.phpt | 26 ++++++------- tests/PhpGenerator/PhpNamespace.phpt | 16 ++++---- .../ClassType.from.trait.bodies.expect | 27 +++++++++++++ .../expected/ClassType.from.trait.expect | 25 ++++++++++++ .../fixtures/{class-body.phpf => bodies.php} | 0 .../{classes.php74 => classes.74.php} | 0 .../{classes.php80 => classes.80.php} | 4 +- .../{classes.php81 => classes.81.php} | 0 .../fixtures/{enum.php81 => enum.php} | 0 tests/PhpGenerator/fixtures/traits.php | 2 + tests/PhpGenerator/invalidNames.phpt | 6 +-- 23 files changed, 114 insertions(+), 62 deletions(-) rename tests/PhpGenerator/fixtures/{class-body.phpf => bodies.php} (100%) rename tests/PhpGenerator/fixtures/{classes.php74 => classes.74.php} (100%) rename tests/PhpGenerator/fixtures/{classes.php80 => classes.80.php} (92%) rename tests/PhpGenerator/fixtures/{classes.php81 => classes.81.php} (100%) rename tests/PhpGenerator/fixtures/{enum.php81 => enum.php} (100%) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index c1ce1d99..45c4ac66 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -14,7 +14,7 @@ jobs: coverage: none - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress - - run: php temp/code-checker/code-checker --strict-types --no-progress + - run: php temp/code-checker/code-checker --strict-types --no-progress --ignore "tests/*/fixtures" nette_cs: diff --git a/tests/PhpGenerator/ClassType.from.74.phpt b/tests/PhpGenerator/ClassType.from.74.phpt index ba13d539..a5c9c338 100644 --- a/tests/PhpGenerator/ClassType.from.74.phpt +++ b/tests/PhpGenerator/ClassType.from.74.phpt @@ -10,7 +10,7 @@ use Nette\PhpGenerator\ClassType; require __DIR__ . '/../bootstrap.php'; -require __DIR__ . '/fixtures/classes.php74'; +require __DIR__ . '/fixtures/classes.74.php'; $res[] = ClassType::from(new Abc\Class7); diff --git a/tests/PhpGenerator/ClassType.from.80.phpt b/tests/PhpGenerator/ClassType.from.80.phpt index 3aceff1c..1cb2b53f 100644 --- a/tests/PhpGenerator/ClassType.from.80.phpt +++ b/tests/PhpGenerator/ClassType.from.80.phpt @@ -10,7 +10,7 @@ use Nette\PhpGenerator\ClassType; require __DIR__ . '/../bootstrap.php'; -require __DIR__ . '/fixtures/classes.php80'; +require __DIR__ . '/fixtures/classes.80.php'; $res[] = ClassType::from(new Abc\Class8(null)); $res[] = ClassType::from(new Abc\Class9); diff --git a/tests/PhpGenerator/ClassType.from.81.phpt b/tests/PhpGenerator/ClassType.from.81.phpt index c0379272..dbdd5659 100644 --- a/tests/PhpGenerator/ClassType.from.81.phpt +++ b/tests/PhpGenerator/ClassType.from.81.phpt @@ -10,7 +10,7 @@ use Nette\PhpGenerator\ClassType; require __DIR__ . '/../bootstrap.php'; -require __DIR__ . '/fixtures/classes.php81'; +require __DIR__ . '/fixtures/classes.81.php'; $res[] = ClassType::from(new Abc\Class11); diff --git a/tests/PhpGenerator/ClassType.from.bodies.phpt b/tests/PhpGenerator/ClassType.from.bodies.phpt index c080e2ea..0151bcc9 100644 --- a/tests/PhpGenerator/ClassType.from.bodies.phpt +++ b/tests/PhpGenerator/ClassType.from.bodies.phpt @@ -7,7 +7,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -require __DIR__ . '/fixtures/class-body.phpf'; +require __DIR__ . '/fixtures/bodies.php'; Assert::exception(function () { diff --git a/tests/PhpGenerator/ClassType.from.enum.phpt b/tests/PhpGenerator/ClassType.from.enum.phpt index d16e770e..45076eb8 100644 --- a/tests/PhpGenerator/ClassType.from.enum.phpt +++ b/tests/PhpGenerator/ClassType.from.enum.phpt @@ -10,7 +10,7 @@ use Nette\PhpGenerator\ClassType; require __DIR__ . '/../bootstrap.php'; -require __DIR__ . '/fixtures/enum.php81'; +require __DIR__ . '/fixtures/enum.php'; $res[] = ClassType::from(Abc\Enum1::class); $res[] = ClassType::from(Abc\Enum2::class); diff --git a/tests/PhpGenerator/ClassType.from.phpt b/tests/PhpGenerator/ClassType.from.phpt index 221d02b1..7d170a0e 100644 --- a/tests/PhpGenerator/ClassType.from.phpt +++ b/tests/PhpGenerator/ClassType.from.phpt @@ -11,7 +11,7 @@ use Nette\PhpGenerator\Factory; require __DIR__ . '/../bootstrap.php'; -@require __DIR__ . '/fixtures/classes.php'; // triggers error 'Required parameter $c follows optional parameter $a' +require __DIR__ . '/fixtures/classes.php'; $res[] = ClassType::from(Abc\Interface1::class); $res[] = ClassType::from(Abc\Interface2::class); diff --git a/tests/PhpGenerator/ClassType.from.trait.phpt b/tests/PhpGenerator/ClassType.from.trait.phpt index 7db53c93..60ad78cb 100644 --- a/tests/PhpGenerator/ClassType.from.trait.phpt +++ b/tests/PhpGenerator/ClassType.from.trait.phpt @@ -1,9 +1,5 @@ getReturnType()); - $method = Method::from(A::class . '::testScalar'); + $method = Method::from([A::class, 'testScalar']); Assert::same('string', $method->getReturnType()); // generating methods with return type declarations diff --git a/tests/PhpGenerator/Method.scalarParameters.phpt b/tests/PhpGenerator/Method.scalarParameters.phpt index ec2deef4..fac07279 100644 --- a/tests/PhpGenerator/Method.scalarParameters.phpt +++ b/tests/PhpGenerator/Method.scalarParameters.phpt @@ -22,16 +22,16 @@ interface Foo function scalars(string $a, bool $b, int $c, float $d); } -$method = Method::from(Foo::class . '::scalars'); +$method = Method::from([Foo::class, 'scalars']); Assert::same('string', $method->getParameters()['a']->getType()); -$method = Method::from(Foo::class . '::scalars'); +$method = Method::from([Foo::class, 'scalars']); Assert::same('bool', $method->getParameters()['b']->getType()); -$method = Method::from(Foo::class . '::scalars'); +$method = Method::from([Foo::class, 'scalars']); Assert::same('int', $method->getParameters()['c']->getType()); -$method = Method::from(Foo::class . '::scalars'); +$method = Method::from([Foo::class, 'scalars']); Assert::same('float', $method->getParameters()['d']->getType()); diff --git a/tests/PhpGenerator/Method.variadics.phpt b/tests/PhpGenerator/Method.variadics.phpt index 3de0c581..287a05ab 100644 --- a/tests/PhpGenerator/Method.variadics.phpt +++ b/tests/PhpGenerator/Method.variadics.phpt @@ -22,10 +22,10 @@ interface Variadics function bar($foo, array &...$bar); } -$method = Method::from(Variadics::class . '::foo'); +$method = Method::from([Variadics::class, 'foo']); Assert::true($method->isVariadic()); -$method = Method::from(Variadics::class . '::bar'); +$method = Method::from([Variadics::class, 'bar']); Assert::true($method->isVariadic()); Assert::true($method->getParameters()['bar']->isReference()); Assert::same('array', $method->getParameters()['bar']->getType()); diff --git a/tests/PhpGenerator/PhpFile.phpt b/tests/PhpGenerator/PhpFile.phpt index 677afa18..a301d364 100644 --- a/tests/PhpGenerator/PhpFile.phpt +++ b/tests/PhpGenerator/PhpFile.phpt @@ -31,10 +31,10 @@ $traitC = $namespaceFoo->addTrait('C'); Assert::same($namespaceFoo, $traitC->getNamespace()); $classA - ->addImplement('Foo\\A') - ->addTrait('Foo\\C') - ->addImplement('Bar\\C') - ->addTrait('Bar\\D'); + ->addImplement('Foo\A') + ->addTrait('Foo\C') + ->addImplement('Bar\C') + ->addTrait('Bar\D'); $namespaceBar = $file->addNamespace('Bar'); @@ -52,22 +52,22 @@ $enumEN = $namespaceBar->addEnum('EN'); Assert::same($enumEN->getNamespace(), $namespaceBar); $classB - ->addExtend('Foo\\A') - ->addImplement('Foo\\B') - ->addTrait('Foo\\C'); + ->addExtend('Foo\A') + ->addImplement('Foo\B') + ->addTrait('Foo\C'); -$classE = $file->addClass('Baz\\E'); +$classE = $file->addClass('Baz\E'); Assert::same($file->addNamespace('Baz'), $classE->getNamespace()); -$interfaceF = $file->addInterface('Baz\\F'); +$interfaceF = $file->addInterface('Baz\F'); Assert::same($file->addNamespace('Baz'), $interfaceF->getNamespace()); $interfaceF - ->addExtend('Foo\\B') - ->addExtend('Bar\\C'); + ->addExtend('Foo\B') + ->addExtend('Bar\C'); -$traitG = $file->addTrait('Baz\\G'); +$traitG = $file->addTrait('Baz\G'); Assert::same($file->addNamespace('Baz'), $traitG->getNamespace()); @@ -75,7 +75,7 @@ sameFile(__DIR__ . '/expected/PhpFile.regular.expect', (string) $file); $file->addClass('H'); -$file->addClass('FooBar\\I'); +$file->addClass('FooBar\I'); sameFile(__DIR__ . '/expected/PhpFile.bracketed.expect', (string) $file); diff --git a/tests/PhpGenerator/PhpNamespace.phpt b/tests/PhpGenerator/PhpNamespace.phpt index 68075767..28ee045d 100644 --- a/tests/PhpGenerator/PhpNamespace.phpt +++ b/tests/PhpGenerator/PhpNamespace.phpt @@ -43,7 +43,7 @@ Assert::same('A&\Countable', $namespace->unresolveType('foo\A&Countable')); Assert::same('', $namespace->unresolveType('')); $namespace->addUse('Bar\C'); -Assert::same(['C' => 'Bar\\C'], $namespace->getUses()); +Assert::same(['C' => 'Bar\C'], $namespace->getUses()); Assert::same('\Bar', $namespace->unresolveName('Bar')); Assert::same('C', $namespace->unresolveName('\bar\C')); @@ -70,16 +70,16 @@ Assert::exception(function () use ($namespace) { }, Nette\InvalidStateException::class, "Alias 'C' used already for 'Bar\\C', cannot use for 'Foo\\C'."); $classA - ->addImplement('Foo\\A') - ->addImplement('Bar\\C') - ->addTrait('Bar\\D') - ->addAttribute('Foo\\A'); + ->addImplement('Foo\A') + ->addImplement('Bar\C') + ->addTrait('Bar\D') + ->addAttribute('Foo\A'); $method = $classA->addMethod('test'); -$method->addAttribute('Foo\\A'); -$method->setReturnType('static|Foo\\A'); +$method->addAttribute('Foo\A'); +$method->setReturnType('static|Foo\A'); -$method->addParameter('a')->setType('Bar\C')->addAttribute('Bar\\D'); +$method->addParameter('a')->setType('Bar\C')->addAttribute('Bar\D'); $method->addParameter('b')->setType('self'); $method->addParameter('c')->setType('parent'); $method->addParameter('d')->setType('array'); diff --git a/tests/PhpGenerator/expected/ClassType.from.trait.bodies.expect b/tests/PhpGenerator/expected/ClassType.from.trait.bodies.expect index f0e13493..21299056 100644 --- a/tests/PhpGenerator/expected/ClassType.from.trait.bodies.expect +++ b/tests/PhpGenerator/expected/ClassType.from.trait.bodies.expect @@ -3,6 +3,7 @@ */ trait Trait1 { + public static $s1; public $x1; @@ -12,9 +13,18 @@ trait Trait1 } } +trait Trait1b +{ + public function f1() + { + echo 'Trait1b::f1'; + } +} + trait Trait2 { protected $x2; + public static $s1; public $x1; @@ -30,9 +40,21 @@ trait Trait2 } } +class ParentClass +{ + public $x1; + + + public function f1() + { + echo 'ParentClass::f1'; + } +} + class Class1 extends ParentClass { protected $x2; + public static $s1; public function f1() @@ -51,6 +73,7 @@ class Class2 extends ParentClass { public $x1; protected $x2; + public static $s1; public function f1() @@ -67,8 +90,10 @@ class Class2 extends ParentClass class Class3 extends ParentClass { + /** info */ public $x1; protected $x2; + public static $s1; public function f1() @@ -92,6 +117,7 @@ class Class3 extends ParentClass class Class4 extends ParentClass { protected $x2; + public static $s1; public function aliased() @@ -114,6 +140,7 @@ class Class4 extends ParentClass class Class5 { + public static $s1; public $x1; diff --git a/tests/PhpGenerator/expected/ClassType.from.trait.expect b/tests/PhpGenerator/expected/ClassType.from.trait.expect index ea2c3cd0..7da25afc 100644 --- a/tests/PhpGenerator/expected/ClassType.from.trait.expect +++ b/tests/PhpGenerator/expected/ClassType.from.trait.expect @@ -3,6 +3,7 @@ */ trait Trait1 { + public static $s1; public $x1; @@ -11,9 +12,17 @@ trait Trait1 } } +trait Trait1b +{ + public function f1() + { + } +} + trait Trait2 { protected $x2; + public static $s1; public $x1; @@ -27,9 +36,20 @@ trait Trait2 } } +class ParentClass +{ + public $x1; + + + public function f1() + { + } +} + class Class1 extends ParentClass { protected $x2; + public static $s1; public function f1() @@ -46,6 +66,7 @@ class Class2 extends ParentClass { public $x1; protected $x2; + public static $s1; public function f1() @@ -60,8 +81,10 @@ class Class2 extends ParentClass class Class3 extends ParentClass { + /** info */ public $x1; protected $x2; + public static $s1; public function f1() @@ -82,6 +105,7 @@ class Class3 extends ParentClass class Class4 extends ParentClass { protected $x2; + public static $s1; public function aliased() @@ -101,6 +125,7 @@ class Class4 extends ParentClass class Class5 { + public static $s1; public $x1; diff --git a/tests/PhpGenerator/fixtures/class-body.phpf b/tests/PhpGenerator/fixtures/bodies.php similarity index 100% rename from tests/PhpGenerator/fixtures/class-body.phpf rename to tests/PhpGenerator/fixtures/bodies.php diff --git a/tests/PhpGenerator/fixtures/classes.php74 b/tests/PhpGenerator/fixtures/classes.74.php similarity index 100% rename from tests/PhpGenerator/fixtures/classes.php74 rename to tests/PhpGenerator/fixtures/classes.74.php diff --git a/tests/PhpGenerator/fixtures/classes.php80 b/tests/PhpGenerator/fixtures/classes.80.php similarity index 92% rename from tests/PhpGenerator/fixtures/classes.php80 rename to tests/PhpGenerator/fixtures/classes.80.php index f910388c..0527c5af 100644 --- a/tests/PhpGenerator/fixtures/classes.php80 +++ b/tests/PhpGenerator/fixtures/classes.80.php @@ -7,8 +7,8 @@ class Class8 { public function __construct( - public $a, - private int|string $b = 10, + public $a, + private int|string $b = 10, $c = null, ) { } diff --git a/tests/PhpGenerator/fixtures/classes.php81 b/tests/PhpGenerator/fixtures/classes.81.php similarity index 100% rename from tests/PhpGenerator/fixtures/classes.php81 rename to tests/PhpGenerator/fixtures/classes.81.php diff --git a/tests/PhpGenerator/fixtures/enum.php81 b/tests/PhpGenerator/fixtures/enum.php similarity index 100% rename from tests/PhpGenerator/fixtures/enum.php81 rename to tests/PhpGenerator/fixtures/enum.php diff --git a/tests/PhpGenerator/fixtures/traits.php b/tests/PhpGenerator/fixtures/traits.php index 072a369f..69b5b4bf 100644 --- a/tests/PhpGenerator/fixtures/traits.php +++ b/tests/PhpGenerator/fixtures/traits.php @@ -7,6 +7,7 @@ */ trait Trait1 { + public static $s1; public $x1; @@ -69,6 +70,7 @@ class Class3 extends ParentClass Trait2::f1 as aliased; } + /** info */ public $x1; diff --git a/tests/PhpGenerator/invalidNames.phpt b/tests/PhpGenerator/invalidNames.phpt index 77c3d6ca..d53179eb 100644 --- a/tests/PhpGenerator/invalidNames.phpt +++ b/tests/PhpGenerator/invalidNames.phpt @@ -33,7 +33,7 @@ Assert::exception(function () { }, Nette\InvalidArgumentException::class); Assert::exception(function () { - new Nette\PhpGenerator\PhpNamespace('\\abc'); + new Nette\PhpGenerator\PhpNamespace('\abc'); }, Nette\InvalidArgumentException::class); @@ -55,11 +55,11 @@ Assert::exception(function () { }, Nette\InvalidArgumentException::class); Assert::exception(function () { - new Nette\PhpGenerator\ClassType('abc\\abc'); + new Nette\PhpGenerator\ClassType('abc\abc'); }, Nette\InvalidArgumentException::class); Assert::exception(function () { - new Nette\PhpGenerator\ClassType('\\abc'); + new Nette\PhpGenerator\ClassType('\abc'); }, Nette\InvalidArgumentException::class); From fb5cb8b87f909667056508c9586b4d1dba1678c5 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 18 Sep 2021 17:07:13 +0200 Subject: [PATCH 021/266] readme: updated links to API --- readme.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index 79dd8948..415fc4c4 100644 --- a/readme.md +++ b/readme.md @@ -44,7 +44,7 @@ composer require nette/php-generator Classes ------- -Let's start with a straightforward example of generating class using [ClassType](https://api.nette.org/3.0/Nette/PhpGenerator/ClassType.html): +Let's start with a straightforward example of generating class using [ClassType](https://api.nette.org/php-generator/Nette/PhpGenerator/ClassType.html): ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -83,7 +83,7 @@ $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); ``` -We can add constants ([Constant](https://api.nette.org/3.0/Nette/PhpGenerator/Constant.html)) and properties ([Property](https://api.nette.org/3.0/Nette/PhpGenerator/Property.html)): +We can add constants ([Constant](https://api.nette.org/php-generator/Nette/PhpGenerator/Constant.html)) and properties ([Property](https://api.nette.org/php-generator/Nette/PhpGenerator/Property.html)): ```php $class->addConstant('ID', 123) @@ -358,7 +358,7 @@ $obj = new class ($val) { Global Function --------------- -Code of functions will generate class [GlobalFunction](https://api.nette.org/3.0/Nette/PhpGenerator/GlobalFunction.html): +Code of functions will generate class [GlobalFunction](https://api.nette.org/php-generator/Nette/PhpGenerator/GlobalFunction.html): ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -383,7 +383,7 @@ function foo($a, $b) Closure ------- -Code of closures will generate class [Closure](https://api.nette.org/3.0/Nette/PhpGenerator/Closure.html): +Code of closures will generate class [Closure](https://api.nette.org/php-generator/Nette/PhpGenerator/Closure.html): ```php $closure = new Nette\PhpGenerator\Closure; @@ -557,7 +557,7 @@ function foo($a) Namespace --------- -Classes, traits and interfaces (hereinafter classes) can be grouped into namespaces ([PhpNamespace](https://api.nette.org/3.0/Nette/PhpGenerator/PhpNamespace.html)): +Classes, traits and interfaces (hereinafter classes) can be grouped into namespaces ([PhpNamespace](https://api.nette.org/php-generator/Nette/PhpGenerator/PhpNamespace.html)): ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); @@ -641,7 +641,7 @@ echo $printer->printNamespace($namespace); PHP Files --------- -Classes and namespaces can be grouped into PHP files represented by the class [PhpFile](https://api.nette.org/3.0/Nette/PhpGenerator/PhpFile.html): +Classes and namespaces can be grouped into PHP files represented by the class [PhpFile](https://api.nette.org/php-generator/Nette/PhpGenerator/PhpFile.html): ```php $file = new Nette\PhpGenerator\PhpFile; From 479c2a703f688862f74cfefd253f1b7f8a602507 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 15 Sep 2021 13:11:55 +0200 Subject: [PATCH 022/266] readme: fixes --- readme.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/readme.md b/readme.md index 415fc4c4..93fa81fd 100644 --- a/readme.md +++ b/readme.md @@ -413,7 +413,7 @@ You can also print closure as arrow function using printer: ```php $closure = new Nette\PhpGenerator\Closure; -$closure->setBody('return $a + $b;'); +$closure->setBody('$a + $b'); $closure->addParameter('a'); $closure->addParameter('b'); @@ -430,7 +430,7 @@ fn($a, $b) => $a + $b Attributes ---------- -You can add PHP 8 attributes to all classes, methods, properties, constants, functions, closures and parameters. +You can add PHP 8 attributes to all classes, methods, properties, constants, enum cases, functions, closures and parameters. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -495,7 +495,7 @@ Simple placeholders `?` $str = 'any string'; $num = 3; $function = new Nette\PhpGenerator\GlobalFunction('foo'); -$function->addBody('return strlen(?, ?);', [$str, $num]); +$function->addBody('return substr(?, ?);', [$str, $num]); echo $function; ``` @@ -504,7 +504,7 @@ Result: ```php function foo() { - return strlen('any string', 3); + return substr('any string', 3); } ``` @@ -557,7 +557,7 @@ function foo($a) Namespace --------- -Classes, traits and interfaces (hereinafter classes) can be grouped into namespaces ([PhpNamespace](https://api.nette.org/php-generator/Nette/PhpGenerator/PhpNamespace.html)): +Classes, traits, interfaces and enums (hereinafter classes) can be grouped into namespaces ([PhpNamespace](https://api.nette.org/php-generator/Nette/PhpGenerator/PhpNamespace.html)): ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); @@ -685,7 +685,7 @@ class A Generate using Reflection ------------------------- -Another common use case is to create class or method based on existing ones: +Another common use case is to create class or function based on existing one: ```php $class = Nette\PhpGenerator\ClassType::from(PDO::class); @@ -697,7 +697,7 @@ $closure = Nette\PhpGenerator\Closure::from( ); ``` -Method bodies are empty by default. If you want to load them as well, use this way +Function and method bodies are empty by default. If you want to load them as well, use this way (it requires `nikic/php-parser` to be installed): ```php From 372fdcbd5aeff4506eea92793051008391b09f94 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 17 Sep 2021 17:20:00 +0200 Subject: [PATCH 023/266] Factory::fromClassReflection() added option to materialize traits or not [Closes #89] --- src/PhpGenerator/ClassType.php | 4 +- src/PhpGenerator/Factory.php | 36 ++++++-- tests/PhpGenerator/ClassType.from.trait.phpt | 18 +++- ...Type.from.trait-materialize.bodies.expect} | 0 ...> ClassType.from.trait-materialize.expect} | 0 .../ClassType.from.trait-use.bodies.expect | 91 +++++++++++++++++++ .../expected/ClassType.from.trait-use.expect | 84 +++++++++++++++++ 7 files changed, 219 insertions(+), 14 deletions(-) rename tests/PhpGenerator/expected/{ClassType.from.trait.bodies.expect => ClassType.from.trait-materialize.bodies.expect} (100%) rename tests/PhpGenerator/expected/{ClassType.from.trait.expect => ClassType.from.trait-materialize.expect} (100%) create mode 100644 tests/PhpGenerator/expected/ClassType.from.trait-use.bodies.expect create mode 100644 tests/PhpGenerator/expected/ClassType.from.trait-use.expect diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 6e0e297c..e0e11765 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -99,9 +99,9 @@ public static function enum(string $name = null, PhpNamespace $namespace = null) /** * @param string|object $class */ - public static function from($class): self + public static function from($class, bool $withBodies = false, bool $materializeTraits = true): self { - return (new Factory)->fromClassReflection(new \ReflectionClass($class)); + return (new Factory)->fromClassReflection(new \ReflectionClass($class), $withBodies, $materializeTraits); } diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 6b58d5b8..f27c0071 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -10,6 +10,7 @@ namespace Nette\PhpGenerator; use Nette; +use Nette\Utils\Reflection; /** @@ -23,8 +24,11 @@ final class Factory private $extractorCache = []; - public function fromClassReflection(\ReflectionClass $from, bool $withBodies = false): ClassType - { + public function fromClassReflection( + \ReflectionClass $from, + bool $withBodies = false, + bool $materializeTraits = true + ): ClassType { if ($withBodies && $from->isAnonymous()) { throw new Nette\NotSupportedException('The $withBodies parameter cannot be used for anonymous functions.'); } @@ -66,8 +70,12 @@ public function fromClassReflection(\ReflectionClass $from, bool $withBodies = f $props = []; foreach ($from->getProperties() as $prop) { + $declaringClass = $materializeTraits + ? $prop->getDeclaringClass() + : Reflection::getPropertyDeclaringClass($prop); + if ($prop->isDefault() - && $prop->getDeclaringClass()->name === $from->name + && $declaringClass->name === $from->name && (PHP_VERSION_ID < 80000 || !$prop->isPromoted()) && !$class->isEnum() ) { @@ -78,24 +86,32 @@ public function fromClassReflection(\ReflectionClass $from, bool $withBodies = f $methods = []; foreach ($from->getMethods() as $method) { + $realMethod = Reflection::getMethodDeclaringMethod($method); + $declaringClass = ($materializeTraits ? $method : $realMethod)->getDeclaringClass(); + if ( - $method->getDeclaringClass()->name === $from->name + $declaringClass->name === $from->name && (!$enumIface || !method_exists($enumIface, $method->name)) ) { $methods[] = $m = $this->fromMethodReflection($method); if ($withBodies) { - $srcMethod = Nette\Utils\Reflection::getMethodDeclaringMethod($method); - $srcClass = $srcMethod->getDeclaringClass(); - $bodies = &$this->bodyCache[$srcClass->name]; - $bodies = $bodies ?? $this->getExtractor($srcClass)->extractMethodBodies($srcClass->name); - if (isset($bodies[$srcMethod->name])) { - $m->setBody($bodies[$srcMethod->name]); + $realMethodClass = $realMethod->getDeclaringClass(); + $bodies = &$this->bodyCache[$realMethodClass->name]; + $bodies = $bodies ?? $this->getExtractor($realMethodClass)->extractMethodBodies($realMethodClass->name); + if (isset($bodies[$realMethod->name])) { + $m->setBody($bodies[$realMethod->name]); } } } } $class->setMethods($methods); + if (!$materializeTraits) { + foreach ($from->getTraitNames() as $trait) { + $class->addTrait($trait); + } + } + $consts = $cases = []; foreach ($from->getReflectionConstants() as $const) { if ($class->isEnum() && $from->hasCase($const->name)) { diff --git a/tests/PhpGenerator/ClassType.from.trait.phpt b/tests/PhpGenerator/ClassType.from.trait.phpt index 60ad78cb..436f3ed0 100644 --- a/tests/PhpGenerator/ClassType.from.trait.phpt +++ b/tests/PhpGenerator/ClassType.from.trait.phpt @@ -24,11 +24,25 @@ $res = array_map(function ($class) { return ClassType::from($class); }, $classes); -sameFile(__DIR__ . '/expected/ClassType.from.trait.expect', implode("\n", $res)); +sameFile(__DIR__ . '/expected/ClassType.from.trait-materialize.expect', implode("\n", $res)); $res = array_map(function ($class) { return ClassType::withBodiesFrom($class); }, $classes); -sameFile(__DIR__ . '/expected/ClassType.from.trait.bodies.expect', implode("\n", $res)); +sameFile(__DIR__ . '/expected/ClassType.from.trait-materialize.bodies.expect', implode("\n", $res)); + + +$res = array_map(function ($class) { + return ClassType::from($class, /*withBodies:*/ false, /*materializeTraits:*/ false); +}, $classes); + +sameFile(__DIR__ . '/expected/ClassType.from.trait-use.expect', implode("\n", $res)); + + +$res = array_map(function ($class) { + return ClassType::from($class, /*withBodies:*/ true, /*materializeTraits:*/ false); +}, $classes); + +sameFile(__DIR__ . '/expected/ClassType.from.trait-use.bodies.expect', implode("\n", $res)); diff --git a/tests/PhpGenerator/expected/ClassType.from.trait.bodies.expect b/tests/PhpGenerator/expected/ClassType.from.trait-materialize.bodies.expect similarity index 100% rename from tests/PhpGenerator/expected/ClassType.from.trait.bodies.expect rename to tests/PhpGenerator/expected/ClassType.from.trait-materialize.bodies.expect diff --git a/tests/PhpGenerator/expected/ClassType.from.trait.expect b/tests/PhpGenerator/expected/ClassType.from.trait-materialize.expect similarity index 100% rename from tests/PhpGenerator/expected/ClassType.from.trait.expect rename to tests/PhpGenerator/expected/ClassType.from.trait-materialize.expect diff --git a/tests/PhpGenerator/expected/ClassType.from.trait-use.bodies.expect b/tests/PhpGenerator/expected/ClassType.from.trait-use.bodies.expect new file mode 100644 index 00000000..0f7909bb --- /dev/null +++ b/tests/PhpGenerator/expected/ClassType.from.trait-use.bodies.expect @@ -0,0 +1,91 @@ +/** + * Trait1 + */ +trait Trait1 +{ + public static $s1; + public $x1; + + + public function f1() + { + echo 'Trait1::f1'; + } +} + +trait Trait1b +{ + public function f1() + { + echo 'Trait1b::f1'; + } +} + +trait Trait2 +{ + use Trait1; + + protected $x2; + + + public function f2() + { + echo 'Trait2::f2'; + } +} + +class ParentClass +{ + public $x1; + + + public function f1() + { + echo 'ParentClass::f1'; + } +} + +class Class1 extends ParentClass +{ + use Trait2; +} + +class Class2 extends ParentClass +{ + use Trait2; + + public function f1() + { + echo 'Class2::f1'; + } +} + +class Class3 extends ParentClass +{ + use Trait2; + + /** info */ + public $x1; + + + public function f1() + { + echo 'Class3::f1'; + } +} + +class Class4 extends ParentClass +{ + use Trait2; + + public function aliased() + { + echo 'Class4::aliased'; + } +} + +class Class5 +{ + use Trait1; + use Trait1b; +} diff --git a/tests/PhpGenerator/expected/ClassType.from.trait-use.expect b/tests/PhpGenerator/expected/ClassType.from.trait-use.expect new file mode 100644 index 00000000..eece0f72 --- /dev/null +++ b/tests/PhpGenerator/expected/ClassType.from.trait-use.expect @@ -0,0 +1,84 @@ +/** + * Trait1 + */ +trait Trait1 +{ + public static $s1; + public $x1; + + + public function f1() + { + } +} + +trait Trait1b +{ + public function f1() + { + } +} + +trait Trait2 +{ + use Trait1; + + protected $x2; + + + public function f2() + { + } +} + +class ParentClass +{ + public $x1; + + + public function f1() + { + } +} + +class Class1 extends ParentClass +{ + use Trait2; +} + +class Class2 extends ParentClass +{ + use Trait2; + + public function f1() + { + } +} + +class Class3 extends ParentClass +{ + use Trait2; + + /** info */ + public $x1; + + + public function f1() + { + } +} + +class Class4 extends ParentClass +{ + use Trait2; + + public function aliased() + { + } +} + +class Class5 +{ + use Trait1; + use Trait1b; +} From 61ad0bf3b997493fc5ba3d08a9ce0bd8fd908442 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 27 May 2020 13:08:58 +0200 Subject: [PATCH 024/266] Factory: generates trait resolutions --- src/PhpGenerator/Factory.php | 13 +++++++++++-- .../ClassType.from.trait-materialize.bodies.expect | 4 ++-- .../ClassType.from.trait-materialize.expect | 4 ++-- .../expected/ClassType.from.trait-use.bodies.expect | 8 ++++++-- .../expected/ClassType.from.trait-use.expect | 8 ++++++-- tests/PhpGenerator/fixtures/traits.php | 8 +++++--- 6 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index f27c0071..689050db 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -84,7 +84,7 @@ public function fromClassReflection( } $class->setProperties($props); - $methods = []; + $methods = $resolutions = []; foreach ($from->getMethods() as $method) { $realMethod = Reflection::getMethodDeclaringMethod($method); $declaringClass = ($materializeTraits ? $method : $realMethod)->getDeclaringClass(); @@ -103,12 +103,21 @@ public function fromClassReflection( } } } + + $modifier = $realMethod->getModifiers() !== $method->getModifiers() + ? ' ' . $this->getVisibility($method) + : null; + $alias = $realMethod->name !== $method->name ? ' ' . $method->name : ''; + if ($modifier || $alias) { + $resolutions[] = $realMethod->name . ' as' . $modifier . $alias; + } } $class->setMethods($methods); if (!$materializeTraits) { foreach ($from->getTraitNames() as $trait) { - $class->addTrait($trait); + $class->addTrait($trait, $resolutions); + $resolutions = []; } } diff --git a/tests/PhpGenerator/expected/ClassType.from.trait-materialize.bodies.expect b/tests/PhpGenerator/expected/ClassType.from.trait-materialize.bodies.expect index 21299056..5812097e 100644 --- a/tests/PhpGenerator/expected/ClassType.from.trait-materialize.bodies.expect +++ b/tests/PhpGenerator/expected/ClassType.from.trait-materialize.bodies.expect @@ -108,7 +108,7 @@ class Class3 extends ParentClass } - public function aliased() + protected function aliased() { echo 'Trait1::f1'; } @@ -144,7 +144,7 @@ class Class5 public $x1; - public function f1() + private function f1() { echo 'Trait1b::f1'; } diff --git a/tests/PhpGenerator/expected/ClassType.from.trait-materialize.expect b/tests/PhpGenerator/expected/ClassType.from.trait-materialize.expect index 7da25afc..8d31386c 100644 --- a/tests/PhpGenerator/expected/ClassType.from.trait-materialize.expect +++ b/tests/PhpGenerator/expected/ClassType.from.trait-materialize.expect @@ -97,7 +97,7 @@ class Class3 extends ParentClass } - public function aliased() + protected function aliased() { } } @@ -129,7 +129,7 @@ class Class5 public $x1; - public function f1() + private function f1() { } } diff --git a/tests/PhpGenerator/expected/ClassType.from.trait-use.bodies.expect b/tests/PhpGenerator/expected/ClassType.from.trait-use.bodies.expect index 0f7909bb..bdd46f66 100644 --- a/tests/PhpGenerator/expected/ClassType.from.trait-use.bodies.expect +++ b/tests/PhpGenerator/expected/ClassType.from.trait-use.bodies.expect @@ -62,7 +62,9 @@ class Class2 extends ParentClass class Class3 extends ParentClass { - use Trait2; + use Trait2 { + f1 as protected aliased; + } /** info */ public $x1; @@ -86,6 +88,8 @@ class Class4 extends ParentClass class Class5 { - use Trait1; + use Trait1 { + f1 as private; + } use Trait1b; } diff --git a/tests/PhpGenerator/expected/ClassType.from.trait-use.expect b/tests/PhpGenerator/expected/ClassType.from.trait-use.expect index eece0f72..85c990ed 100644 --- a/tests/PhpGenerator/expected/ClassType.from.trait-use.expect +++ b/tests/PhpGenerator/expected/ClassType.from.trait-use.expect @@ -57,7 +57,9 @@ class Class2 extends ParentClass class Class3 extends ParentClass { - use Trait2; + use Trait2 { + f1 as protected aliased; + } /** info */ public $x1; @@ -79,6 +81,8 @@ class Class4 extends ParentClass class Class5 { - use Trait1; + use Trait1 { + f1 as private; + } use Trait1b; } diff --git a/tests/PhpGenerator/fixtures/traits.php b/tests/PhpGenerator/fixtures/traits.php index 69b5b4bf..c9d009d2 100644 --- a/tests/PhpGenerator/fixtures/traits.php +++ b/tests/PhpGenerator/fixtures/traits.php @@ -67,7 +67,8 @@ public function f1() class Class3 extends ParentClass { use Trait2 { - Trait2::f1 as aliased; + f1 as protected aliased; + f1 as private; // ignored because Class3::f1() exists } /** info */ @@ -84,7 +85,7 @@ public function f1() class Class4 extends ParentClass { use Trait2 { - Trait2::f1 as aliased; + f1 as protected aliased; // ignored because Class4::aliased() exists } @@ -107,6 +108,7 @@ public function f1() class Class5 { use Trait1, Trait1b { - Trait1b::f1 insteadof Trait1; + Trait1b::f1 insteadof Trait1; // not yet supported + Trait1b::f1 as private; } } From 8185e0cf3132feebf469dd426b3e4536cf16993f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 14 Sep 2021 13:46:19 +0200 Subject: [PATCH 025/266] GlobalFunction::from() added $withBody --- src/PhpGenerator/GlobalFunction.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhpGenerator/GlobalFunction.php b/src/PhpGenerator/GlobalFunction.php index 0d40bae7..08faaf46 100644 --- a/src/PhpGenerator/GlobalFunction.php +++ b/src/PhpGenerator/GlobalFunction.php @@ -25,9 +25,9 @@ final class GlobalFunction use Traits\CommentAware; use Traits\AttributeAware; - public static function from(string $function): self + public static function from(string $function, bool $withBody = false): self { - return (new Factory)->fromFunctionReflection(new \ReflectionFunction($function)); + return (new Factory)->fromFunctionReflection(new \ReflectionFunction($function), $withBody); } From 1e4721db16ddcbb7ef585380622931d6b596f8e3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 15 Sep 2021 15:05:48 +0200 Subject: [PATCH 026/266] Dumper: prints closures as func(...) in PHP 8.1 --- src/PhpGenerator/Dumper.php | 4 +++- tests/PhpGenerator/Dumper.dump().phpt | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 52c10dd8..9ebbc06b 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -129,7 +129,9 @@ private function dumpObject(&$var, array $parents, int $level): string } elseif ($var instanceof \Closure) { $inner = Nette\Utils\Callback::unwrap($var); if (Nette\Utils\Callback::isStatic($inner)) { - return '\Closure::fromCallable(' . $this->dump($inner) . ')'; + return PHP_VERSION_ID < 80100 + ? '\Closure::fromCallable(' . $this->dump($inner) . ')' + : implode('::', (array) $inner) . '(...)'; } throw new Nette\InvalidArgumentException('Cannot dump closure.'); } diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index be3ee5e3..837f7f29 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -129,12 +129,16 @@ Assert::exception(function () { // closures Assert::same( - "\\Closure::fromCallable('strlen')", + PHP_VERSION_ID < 80100 + ? "\\Closure::fromCallable('strlen')" + : 'strlen(...)', $dumper->dump(Closure::fromCallable('strlen')) ); Assert::same( - "\\Closure::fromCallable(['Nette\\PhpGenerator\\ClassType', 'from'])", + PHP_VERSION_ID < 80100 + ? "\\Closure::fromCallable(['Nette\\PhpGenerator\\ClassType', 'from'])" + : 'Nette\PhpGenerator\ClassType::from(...)', $dumper->dump(Closure::fromCallable([Nette\PhpGenerator\ClassType::class, 'from'])) ); From 1495adb5b60fe5e3668eef9592621f4dcef864be Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 16 Sep 2021 02:12:32 +0200 Subject: [PATCH 027/266] ClassType: constants are 'public' by default --- src/PhpGenerator/ClassType.php | 2 +- tests/PhpGenerator/Printer.phpt | 2 +- tests/PhpGenerator/PsrPrinter.phpt | 2 +- tests/PhpGenerator/expected/ClassType.attributes.expect | 2 +- tests/PhpGenerator/expected/ClassType.enum.expect | 2 +- tests/PhpGenerator/expected/ClassType.expect | 2 +- tests/PhpGenerator/expected/Printer.class-alt.expect | 4 ++-- tests/PhpGenerator/expected/Printer.class.expect | 4 ++-- tests/PhpGenerator/expected/PsrPrinter.class.expect | 4 ++-- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index e0e11765..b15e42e0 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -421,7 +421,7 @@ public function getConstants(): array public function addConstant(string $name, $value): Constant { - return $this->consts[$name] = (new Constant($name))->setValue($value); + return $this->consts[$name] = (new Constant($name))->setValue($value)->setPublic(); } diff --git a/tests/PhpGenerator/Printer.phpt b/tests/PhpGenerator/Printer.phpt index 7ffba9a1..11a58554 100644 --- a/tests/PhpGenerator/Printer.phpt +++ b/tests/PhpGenerator/Printer.phpt @@ -27,7 +27,7 @@ $class->addConstant('FORCE_ARRAY', new Literal('Nette\Utils\Json::FORCE_ARRAY')) ->addComment('Commented'); $class->addConstant('MULTILINE_LONG', ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, 'dddddddd' => 4, 'eeeeeeee' => 5, 'ffffffff' => 6]); -$class->addConstant('SHORT', ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, 'dddddddd' => 4, 'eeeeeeee' => 5, 'ffffffff' => 6]); +$class->addConstant('SHORT', ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, 'dddddddd' => 4, 'eeeeeeee' => 5]); $class->addProperty('handle') ->setVisibility('private') diff --git a/tests/PhpGenerator/PsrPrinter.phpt b/tests/PhpGenerator/PsrPrinter.phpt index 4f2d1464..7e4cba93 100644 --- a/tests/PhpGenerator/PsrPrinter.phpt +++ b/tests/PhpGenerator/PsrPrinter.phpt @@ -26,7 +26,7 @@ $class->addConstant('FORCE_ARRAY', new Literal('Nette\Utils\Json::FORCE_ARRAY')) ->addComment('Commented'); $class->addConstant('MULTILINE_LONG', ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, 'dddddddd' => 4, 'eeeeeeee' => 5, 'ffffffff' => 6]); -$class->addConstant('SHORT', ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, 'dddddddd' => 4, 'eeeeeeee' => 5, 'ffffffff' => 6]); +$class->addConstant('SHORT', ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, 'dddddddd' => 4, 'eeeeeeee' => 5]); $class->addProperty('handle') ->setVisibility('private') diff --git a/tests/PhpGenerator/expected/ClassType.attributes.expect b/tests/PhpGenerator/expected/ClassType.attributes.expect index e3b5e82a..b10df65e 100644 --- a/tests/PhpGenerator/expected/ClassType.attributes.expect +++ b/tests/PhpGenerator/expected/ClassType.attributes.expect @@ -9,7 +9,7 @@ class Example /** Commented */ #[ExampleAttribute] #[WithArguments(true)] - const FOO = 123; + public const FOO = 123; /** @var resource */ #[ExampleAttribute] diff --git a/tests/PhpGenerator/expected/ClassType.enum.expect b/tests/PhpGenerator/expected/ClassType.enum.expect index 022cbb54..f681f183 100644 --- a/tests/PhpGenerator/expected/ClassType.enum.expect +++ b/tests/PhpGenerator/expected/ClassType.enum.expect @@ -16,7 +16,7 @@ enum Suit case Hearts; case Spades; - const ACTIVE = false; + public const ACTIVE = false; public function foo() { diff --git a/tests/PhpGenerator/expected/ClassType.expect b/tests/PhpGenerator/expected/ClassType.expect index 758aaf19..090aeaf9 100644 --- a/tests/PhpGenerator/expected/ClassType.expect +++ b/tests/PhpGenerator/expected/ClassType.expect @@ -12,7 +12,7 @@ abstract class Example extends ParentClass implements IExample, IOne } const ROLE = 'admin'; - final const ACTIVE = false; + final public const ACTIVE = false; /** Commented */ private const FORCE_ARRAY = Nette\Utils\Json::FORCE_ARRAY; diff --git a/tests/PhpGenerator/expected/Printer.class-alt.expect b/tests/PhpGenerator/expected/Printer.class-alt.expect index d2edfb47..17defce8 100644 --- a/tests/PhpGenerator/expected/Printer.class-alt.expect +++ b/tests/PhpGenerator/expected/Printer.class-alt.expect @@ -12,7 +12,7 @@ final class Example extends ParentClass implements IExample /** Commented */ private const FORCE_ARRAY = Nette\Utils\Json::FORCE_ARRAY; - const MULTILINE_LONG = [ + public const MULTILINE_LONG = [ 'aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, @@ -21,7 +21,7 @@ final class Example extends ParentClass implements IExample 'ffffffff' => 6, ]; - const SHORT = ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, 'dddddddd' => 4, 'eeeeeeee' => 5, 'ffffffff' => 6]; + public const SHORT = ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, 'dddddddd' => 4, 'eeeeeeee' => 5]; /** @var resource orignal file handle */ private $handle; diff --git a/tests/PhpGenerator/expected/Printer.class.expect b/tests/PhpGenerator/expected/Printer.class.expect index 899d6ec6..dbdc302b 100644 --- a/tests/PhpGenerator/expected/Printer.class.expect +++ b/tests/PhpGenerator/expected/Printer.class.expect @@ -12,7 +12,7 @@ final class Example extends ParentClass implements IExample /** Commented */ private const FORCE_ARRAY = Nette\Utils\Json::FORCE_ARRAY; - const MULTILINE_LONG = [ + public const MULTILINE_LONG = [ 'aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, @@ -21,7 +21,7 @@ final class Example extends ParentClass implements IExample 'ffffffff' => 6, ]; - const SHORT = ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, 'dddddddd' => 4, 'eeeeeeee' => 5, 'ffffffff' => 6]; + public const SHORT = ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, 'dddddddd' => 4, 'eeeeeeee' => 5]; /** @var resource orignal file handle */ private $handle; diff --git a/tests/PhpGenerator/expected/PsrPrinter.class.expect b/tests/PhpGenerator/expected/PsrPrinter.class.expect index ec60b708..5b57db98 100644 --- a/tests/PhpGenerator/expected/PsrPrinter.class.expect +++ b/tests/PhpGenerator/expected/PsrPrinter.class.expect @@ -12,7 +12,7 @@ final class Example extends ParentClass implements IExample /** Commented */ private const FORCE_ARRAY = Nette\Utils\Json::FORCE_ARRAY; - const MULTILINE_LONG = [ + public const MULTILINE_LONG = [ 'aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, @@ -21,7 +21,7 @@ final class Example extends ParentClass implements IExample 'ffffffff' => 6, ]; - const SHORT = ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, 'dddddddd' => 4, 'eeeeeeee' => 5, 'ffffffff' => 6]; + public const SHORT = ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, 'dddddddd' => 4, 'eeeeeeee' => 5]; /** @var resource orignal file handle */ private $handle; From eb7c840ea3b30b7d3bf7c6a26a0d1ceddeb228c9 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 15 Sep 2021 17:11:50 +0200 Subject: [PATCH 028/266] ClassType: removed $namespace from Class(), interface(), trait() --- src/PhpGenerator/ClassType.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index b15e42e0..995b2156 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -72,27 +72,27 @@ final class ClassType private $cases = []; - public static function class(string $name = null, PhpNamespace $namespace = null): self + public static function class(?string $name): self { - return new self($name, $namespace); + return new self($name); } - public static function interface(string $name = null, PhpNamespace $namespace = null): self + public static function interface(string $name): self { - return (new self($name, $namespace))->setType(self::TYPE_INTERFACE); + return (new self($name))->setType(self::TYPE_INTERFACE); } - public static function trait(string $name = null, PhpNamespace $namespace = null): self + public static function trait(string $name): self { - return (new self($name, $namespace))->setType(self::TYPE_TRAIT); + return (new self($name))->setType(self::TYPE_TRAIT); } - public static function enum(string $name = null, PhpNamespace $namespace = null): self + public static function enum(string $name): self { - return (new self($name, $namespace))->setType(self::TYPE_ENUM); + return (new self($name))->setType(self::TYPE_ENUM); } From 464c3fdc863d52715a872a6bd0d9403e88baaae9 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 16 Sep 2021 11:33:59 +0200 Subject: [PATCH 029/266] PhpFile: added getClasses() --- src/PhpGenerator/PhpFile.php | 14 ++++++++++++++ tests/PhpGenerator/PhpFile.phpt | 27 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index cbec86cb..59401cad 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -91,6 +91,20 @@ public function getNamespaces(): array } + /** @return ClassType[] */ + public function getClasses(): array + { + $classes = []; + foreach ($this->namespaces as $n => $namespace) { + $n .= $n ? '\\' : ''; + foreach ($namespace->getClasses() as $c => $class) { + $classes[$n . $c] = $class; + } + } + return $classes; + } + + /** @return static */ public function addUse(string $name, string $alias = null): self { diff --git a/tests/PhpGenerator/PhpFile.phpt b/tests/PhpGenerator/PhpFile.phpt index a301d364..97ba9758 100644 --- a/tests/PhpGenerator/PhpFile.phpt +++ b/tests/PhpGenerator/PhpFile.phpt @@ -79,6 +79,31 @@ $file->addClass('FooBar\I'); sameFile(__DIR__ . '/expected/PhpFile.bracketed.expect', (string) $file); +Assert::same([ + 'Foo', + 'Bar', + 'Baz', + '', + 'FooBar', +], array_keys($file->getNamespaces())); + +Assert::same([ + 'Foo\A', + 'Foo\B', + 'Foo\C', + 'Bar\B', + 'Bar\C', + 'Bar\D', + 'Bar\EN', + 'Baz\E', + 'Baz\F', + 'Baz\G', + 'H', + 'FooBar\I', +], array_keys($file->getClasses())); + + + $file = new PhpFile; $file->addClass('A'); $file->addUse('A') @@ -86,6 +111,8 @@ $file->addUse('A') sameFile(__DIR__ . '/expected/PhpFile.globalNamespace.expect', (string) $file); + + $file = new PhpFile; $file->addComment('This file is auto-generated. DO NOT EDIT!'); $file->setStrictTypes(); From f453e3ffee4e6d7e66118b8377fc2658bea689a0 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 16 Sep 2021 03:07:37 +0200 Subject: [PATCH 030/266] Extractor: NameResolvers replaces nodes --- src/PhpGenerator/Extractor.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index be57b6ca..c02f3cf7 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -45,7 +45,7 @@ private function parseCode(string $code): void $stmts = $parser->parse($this->code); $traverser = new PhpParser\NodeTraverser; - $traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver(null, ['replaceNodes' => false])); + $traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver(null, ['preserveOriginalNames' => true])); $this->statements = $traverser->traverse($stmts); } @@ -96,14 +96,14 @@ private function prepareReplacements(array $statements): array $nodeFinder = new NodeFinder; // name-nodes => resolved fully-qualified name - foreach ($nodeFinder->findInstanceOf($statements, Node\Name::class) as $node) { - if ($node->hasAttribute('resolvedName') - && $node->getAttribute('resolvedName') instanceof Node\Name\FullyQualified + foreach ($nodeFinder->findInstanceOf($statements, Node\Name\FullyQualified::class) as $node) { + if ($node->hasAttribute('originalName') + && $node->getAttribute('originalName') instanceof Node\Name ) { $replacements[] = [ $node->getStartFilePos() - $start, $node->getEndFilePos() - $start, - $node->getAttribute('resolvedName')->toCodeString(), + $node->toCodeString(), ]; } } From 0503161208bff084755ae60aac828dab8e71651b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 18 Sep 2021 16:24:34 +0200 Subject: [PATCH 031/266] PhpFile, PhpNamespace: can contain functions [Closes #80] --- readme.md | 20 +++++++++-------- src/PhpGenerator/PhpFile.php | 22 +++++++++++++++++++ src/PhpGenerator/PhpNamespace.php | 16 ++++++++++++++ src/PhpGenerator/Printer.php | 9 +++++--- tests/PhpGenerator/PhpFile.phpt | 9 ++++++++ .../expected/PhpFile.bracketed.expect | 9 ++++++++ .../expected/PhpFile.regular.expect | 4 ++++ 7 files changed, 77 insertions(+), 12 deletions(-) diff --git a/readme.md b/readme.md index 93fa81fd..538b1db7 100644 --- a/readme.md +++ b/readme.md @@ -641,19 +641,20 @@ echo $printer->printNamespace($namespace); PHP Files --------- -Classes and namespaces can be grouped into PHP files represented by the class [PhpFile](https://api.nette.org/php-generator/Nette/PhpGenerator/PhpFile.html): +Classes, functions and namespaces can be grouped into PHP files represented by the class [PhpFile](https://api.nette.org/php-generator/Nette/PhpGenerator/PhpFile.html): ```php $file = new Nette\PhpGenerator\PhpFile; $file->addComment('This file is auto-generated.'); $file->setStrictTypes(); // adds declare(strict_types=1) -$namespace = $file->addNamespace('Foo'); -$class = $namespace->addClass('A'); -$class->addMethod('hello'); +$class = $file->addClass('Foo\A'); +$function = $file->addFunction('Foo\foo'); -// or insert an existing namespace into the file -// $file->addNamespace(new Nette\PhpGenerator\PhpNamespace('Foo')); +// or +// $namespace = $file->addNamespace('Foo'); +// $class = $namespace->addClass('A'); +// $function = $namespace->addFunction('foo'); echo $file; @@ -676,9 +677,10 @@ namespace Foo; class A { - public function hello() - { - } +} + +function foo() +{ } ``` diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index 59401cad..c1c67bd7 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -84,6 +84,14 @@ public function addNamespace($namespace): PhpNamespace } + public function addFunction(string $name): GlobalFunction + { + return $this + ->addNamespace(Helpers::extractNamespace($name)) + ->addFunction(Helpers::extractShortName($name)); + } + + /** @return PhpNamespace[] */ public function getNamespaces(): array { @@ -105,6 +113,20 @@ public function getClasses(): array } + /** @return GlobalFunction[] */ + public function getFunctions(): array + { + $functions = []; + foreach ($this->namespaces as $n => $namespace) { + $n .= $n ? '\\' : ''; + foreach ($namespace->getFunctions() as $c => $class) { + $functions[$n . $c] = $class; + } + } + return $functions; + } + + /** @return static */ public function addUse(string $name, string $alias = null): self { diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index db31a788..40356e48 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -44,6 +44,9 @@ final class PhpNamespace /** @var ClassType[] */ private $classes = []; + /** @var GlobalFunction[] */ + private $functions = []; + public function __construct(string $name) { @@ -195,6 +198,12 @@ public function addEnum(string $name): ClassType } + public function addFunction(string $name): GlobalFunction + { + return $this->functions[$name] = new GlobalFunction($name); + } + + /** @return ClassType[] */ public function getClasses(): array { @@ -202,6 +211,13 @@ public function getClasses(): array } + /** @return GlobalFunction[] */ + public function getFunctions(): array + { + return $this->functions; + } + + public function __toString(): string { try { diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index f3abc741..23e79cd3 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -203,13 +203,16 @@ public function printNamespace(PhpNamespace $namespace): string $name = $namespace->getName(); $uses = $this->printUses($namespace); - $classes = []; + $items = []; foreach ($namespace->getClasses() as $class) { - $classes[] = $this->printClass($class, $namespace); + $items[] = $this->printClass($class, $namespace); + } + foreach ($namespace->getFunctions() as $function) { + $items[] = $this->printFunction($function, $namespace); } $body = ($uses ? $uses . "\n\n" : '') - . implode("\n", $classes); + . implode("\n", $items); if ($namespace->hasBracketedSyntax()) { return 'namespace' . ($name ? " $name" : '') . "\n{\n" diff --git a/tests/PhpGenerator/PhpFile.phpt b/tests/PhpGenerator/PhpFile.phpt index 97ba9758..a6e245e0 100644 --- a/tests/PhpGenerator/PhpFile.phpt +++ b/tests/PhpGenerator/PhpFile.phpt @@ -70,6 +70,9 @@ $interfaceF $traitG = $file->addTrait('Baz\G'); Assert::same($file->addNamespace('Baz'), $traitG->getNamespace()); +$file->addFunction('Baz\\f2') + ->setReturnType('Foo\B'); + sameFile(__DIR__ . '/expected/PhpFile.regular.expect', (string) $file); @@ -77,6 +80,9 @@ $file->addClass('H'); $file->addClass('FooBar\I'); +$file->addFunction('f1') + ->setBody('return 1;'); + sameFile(__DIR__ . '/expected/PhpFile.bracketed.expect', (string) $file); Assert::same([ @@ -102,6 +108,9 @@ Assert::same([ 'FooBar\I', ], array_keys($file->getClasses())); +Assert::same(['Baz\\f2', 'f1'], array_keys($file->getFunctions())); + + $file = new PhpFile; diff --git a/tests/PhpGenerator/expected/PhpFile.bracketed.expect b/tests/PhpGenerator/expected/PhpFile.bracketed.expect index 12488830..1c07bcb4 100644 --- a/tests/PhpGenerator/expected/PhpFile.bracketed.expect +++ b/tests/PhpGenerator/expected/PhpFile.bracketed.expect @@ -57,6 +57,10 @@ namespace Baz trait G { } + + function f2(): \Foo\B + { + } } @@ -65,6 +69,11 @@ namespace class H { } + + function f1() + { + return 1; + } } diff --git a/tests/PhpGenerator/expected/PhpFile.regular.expect b/tests/PhpGenerator/expected/PhpFile.regular.expect index fc3455f8..c85a6cd3 100644 --- a/tests/PhpGenerator/expected/PhpFile.regular.expect +++ b/tests/PhpGenerator/expected/PhpFile.regular.expect @@ -55,3 +55,7 @@ interface F extends \Foo\B, \Bar\C trait G { } + +function f2(): \Foo\B +{ +} From 74ffdd5d7c970a486215265f5da54856caf2f7e9 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 16 Sep 2021 11:34:19 +0200 Subject: [PATCH 032/266] added ClassType::fromCode() & PhpFile::fromCode() [Closes #79] --- composer.json | 4 +- readme.md | 24 ++ src/PhpGenerator/ClassType.php | 6 + src/PhpGenerator/Extractor.php | 251 +++++++++++++++++- src/PhpGenerator/Factory.php | 17 ++ src/PhpGenerator/PhpFile.php | 6 + tests/PhpGenerator/ClassType.fromCode.phpt | 28 ++ tests/PhpGenerator/Extractor.extractAll.phpt | 89 +++++++ tests/PhpGenerator/Factory.fromClassCode.phpt | 26 ++ tests/PhpGenerator/PhpFile.phpt | 6 + .../expected/Factory.fromCode.74.expect | 13 + .../expected/Factory.fromCode.80.expect | 48 ++++ .../expected/Factory.fromCode.bodies.expect | 85 ++++++ .../expected/Factory.fromCode.enum.expect | 37 +++ .../expected/Factory.fromCode.expect | 106 ++++++++ .../expected/Factory.fromCode.traits.expect | 107 ++++++++ 16 files changed, 849 insertions(+), 4 deletions(-) create mode 100644 tests/PhpGenerator/ClassType.fromCode.phpt create mode 100644 tests/PhpGenerator/Extractor.extractAll.phpt create mode 100644 tests/PhpGenerator/Factory.fromClassCode.phpt create mode 100644 tests/PhpGenerator/expected/Factory.fromCode.74.expect create mode 100644 tests/PhpGenerator/expected/Factory.fromCode.80.expect create mode 100644 tests/PhpGenerator/expected/Factory.fromCode.bodies.expect create mode 100644 tests/PhpGenerator/expected/Factory.fromCode.enum.expect create mode 100644 tests/PhpGenerator/expected/Factory.fromCode.expect create mode 100644 tests/PhpGenerator/expected/Factory.fromCode.traits.expect diff --git a/composer.json b/composer.json index cd4cf1e4..53c9ad75 100644 --- a/composer.json +++ b/composer.json @@ -19,8 +19,8 @@ "nette/utils": "^3.1.2" }, "require-dev": { - "nette/tester": "^2.0", - "nikic/php-parser": "^4.4", + "nette/tester": "^2.4", + "nikic/php-parser": "^4.11", "tracy/tracy": "^2.3", "phpstan/phpstan": "^0.12" }, diff --git a/readme.md b/readme.md index 538b1db7..6940f359 100644 --- a/readme.md +++ b/readme.md @@ -708,6 +708,30 @@ $class = Nette\PhpGenerator\ClassType::withBodiesFrom(MyClass::class); $function = Nette\PhpGenerator\GlobalFunction::withBodyFrom('dump'); ``` +Load class from file +-------------------- + +You can also load classes directly from a PHP file that is not already loaded or string of PHP code: + +```php +$class = Nette\PhpGenerator\ClassType::fromCode(<<fromClassCode($code); + } + + public function __construct(string $name = null, PhpNamespace $namespace = null) { $this->setName($name); diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index c02f3cf7..90eeaab3 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -26,21 +26,26 @@ final class Extractor private $code; private $statements; + private $printer; public function __construct(string $code) { if (!class_exists(ParserFactory::class)) { - throw new Nette\NotSupportedException("PHP-Parser is required to load method bodies, install package 'nikic/php-parser'."); + throw new Nette\NotSupportedException("PHP-Parser is required to load method bodies, install package 'nikic/php-parser' 4.7 or newer."); } + $this->printer = new PhpParser\PrettyPrinter\Standard; $this->parseCode($code); } private function parseCode(string $code): void { + if (substr($code, 0, 5) !== 'code = str_replace("\r\n", "\n", $code); - $lexer = new PhpParser\Lexer(['usedAttributes' => ['startFilePos', 'endFilePos']]); + $lexer = new PhpParser\Lexer\Emulative(['usedAttributes' => ['startFilePos', 'endFilePos', 'comments']]); $parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7, $lexer); $stmts = $parser->parse($this->code); @@ -167,6 +172,248 @@ private function performReplacements(string $s, array $replacements): string } + public function extractAll(): PhpFile + { + $phpFile = new PhpFile; + $namespace = ''; + $visitor = new class extends PhpParser\NodeVisitorAbstract { + public function enterNode(Node $node) + { + return ($this->callback)($node); + } + }; + + $visitor->callback = function (Node $node) use (&$class, &$namespace, $phpFile) { + if ($node instanceof Node\Stmt\DeclareDeclare && $node->key->name === 'strict_types') { + $phpFile->setStrictTypes((bool) $node->value->value); + } elseif ($node instanceof Node\Stmt\Namespace_) { + $namespace = $node->name ? $node->name->toString() : ''; + } elseif ($node instanceof Node\Stmt\Use_) { + $this->addUseToNamespace($node, $phpFile->addNamespace($namespace)); + } elseif ($node instanceof Node\Stmt\Class_) { + if (!$node->name) { + return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + $class = $this->addClassToFile($phpFile, $node); + } elseif ($node instanceof Node\Stmt\Interface_) { + $class = $this->addInterfaceToFile($phpFile, $node); + } elseif ($node instanceof Node\Stmt\Trait_) { + $class = $this->addTraitToFile($phpFile, $node); + } elseif ($node instanceof Node\Stmt\Enum_) { + $class = $this->addEnumToFile($phpFile, $node); + } elseif ($node instanceof Node\Stmt\Function_) { + $this->addFunctionToFile($phpFile, $node); + } elseif ($node instanceof Node\Stmt\TraitUse) { + $this->addTraitToClass($class, $node); + } elseif ($node instanceof Node\Stmt\Property) { + $this->addPropertyToClass($class, $node); + } elseif ($node instanceof Node\Stmt\ClassMethod) { + $this->addMethodToClass($class, $node); + } elseif ($node instanceof Node\Stmt\ClassConst) { + $this->addConstantToClass($class, $node); + } elseif ($node instanceof Node\Stmt\EnumCase) { + $this->addEnumCaseToClass($class, $node); + } + if ($node instanceof Node\FunctionLike) { + return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + }; + + $traverser = new PhpParser\NodeTraverser; + $traverser->addVisitor($visitor); + $traverser->traverse($this->statements); + return $phpFile; + } + + + private function addUseToNamespace(Node\Stmt\Use_ $node, PhpNamespace $namespace): void + { + if ($node->type === $node::TYPE_NORMAL) { + foreach ($node->uses as $use) { + $namespace->addUse($use->name->toString(), $use->alias ? $use->alias->toString() : null); + } + } + } + + + private function addClassToFile(PhpFile $phpFile, Node\Stmt\Class_ $node): ClassType + { + $class = $phpFile->addClass($node->namespacedName->toString()); + if ($node->extends) { + $class->setExtends($node->extends->toString()); + } + foreach ($node->implements as $item) { + $class->addImplement($item->toString()); + } + $class->setFinal($node->isFinal()); + $class->setAbstract($node->isAbstract()); + $this->addCommentAndAttributes($class, $node); + return $class; + } + + + private function addInterfaceToFile(PhpFile $phpFile, Node\Stmt\Interface_ $node): ClassType + { + $class = $phpFile->addInterface($node->namespacedName->toString()); + foreach ($node->extends as $item) { + $class->addExtend($item->toString()); + } + $this->addCommentAndAttributes($class, $node); + return $class; + } + + + private function addTraitToFile(PhpFile $phpFile, Node\Stmt\Trait_ $node): ClassType + { + $class = $phpFile->addTrait($node->namespacedName->toString()); + $this->addCommentAndAttributes($class, $node); + return $class; + } + + + private function addEnumToFile(PhpFile $phpFile, Node\Stmt\Enum_ $node): ClassType + { + $class = $phpFile->addEnum($node->namespacedName->toString()); + foreach ($node->implements as $item) { + $class->addImplement($item->toString()); + } + $this->addCommentAndAttributes($class, $node); + return $class; + } + + + private function addFunctionToFile(PhpFile $phpFile, Node\Stmt\Function_ $node): void + { + $function = $phpFile->addFunction($node->namespacedName->toString()); + $this->setupFunction($function, $node); + } + + + private function addTraitToClass(ClassType $class, Node\Stmt\TraitUse $node): void + { + $res = []; + foreach ($node->adaptations as $item) { + $res[] = trim($this->toPhp($item), ';'); + } + foreach ($node->traits as $trait) { + $class->addTrait($trait->toString(), $res); + $res = []; + } + } + + + private function addPropertyToClass(ClassType $class, Node\Stmt\Property $node): void + { + foreach ($node->props as $item) { + $prop = $class->addProperty($item->name->toString()); + $prop->setStatic($node->isStatic()); + if ($node->isPrivate()) { + $prop->setPrivate(); + } elseif ($node->isProtected()) { + $prop->setProtected(); + } + $prop->setType($node->type ? $this->toPhp($node->type) : null); + if ($item->default) { + $prop->setValue(new Literal($this->toPhp($item->default))); + } + $prop->setReadOnly(method_exists($node, 'isReadonly') && $node->isReadonly()); + $this->addCommentAndAttributes($prop, $node); + } + } + + + private function addMethodToClass(ClassType $class, Node\Stmt\ClassMethod $node): void + { + $method = $class->addMethod($node->name->toString()); + $method->setAbstract($node->isAbstract()); + $method->setFinal($node->isFinal()); + $method->setStatic($node->isStatic()); + if ($node->isPrivate()) { + $method->setPrivate(); + } elseif ($node->isProtected()) { + $method->setProtected(); + } + $this->setupFunction($method, $node); + } + + + private function addConstantToClass(ClassType $class, Node\Stmt\ClassConst $node): void + { + foreach ($node->consts as $item) { + $const = $class->addConstant($item->name->toString(), new Literal($this->toPhp($item->value))); + if ($node->isPrivate()) { + $const->setPrivate(); + } elseif ($node->isProtected()) { + $const->setProtected(); + } + $const->setFinal(method_exists($node, 'isFinal') && $node->isFinal()); + $this->addCommentAndAttributes($const, $node); + } + } + + + private function addEnumCaseToClass(ClassType $class, Node\Stmt\EnumCase $node) + { + $case = $class->addCase($node->name->toString(), $node->expr ? $node->expr->value : null); + $this->addCommentAndAttributes($case, $node); + } + + + private function addCommentAndAttributes($element, Node $node): void + { + if ($node->getDocComment()) { + $comment = $node->getDocComment()->getReformattedText(); + $comment = Helpers::unformatDocComment($comment); + $element->setComment($comment); + } + + foreach ($node->attrGroups ?? [] as $group) { + foreach ($group->attrs as $attribute) { + $args = []; + foreach ($attribute->args as $arg) { + $value = new Literal($this->toPhp($arg)); + if ($arg->name) { + $args[$arg->name->toString()] = $value; + } else { + $args[] = $value; + } + } + $element->addAttribute($attribute->name->toString(), $args); + } + } + } + + + /** + * @param GlobalFunction|Method $function + */ + private function setupFunction($function, Node\FunctionLike $node): void + { + $function->setReturnReference($node->returnsByRef()); + $function->setReturnType($node->getReturnType() ? $this->toPhp($node->getReturnType()) : null); + foreach ($node->params as $item) { + $param = $function->addParameter($item->var->name); + $param->setType($item->type ? $this->toPhp($item->type) : null); + $param->setReference($item->byRef); + $function->setVariadic($item->variadic); + if ($item->default) { + $param->setDefaultValue(new Literal($this->toPhp($item->default))); + } + $this->addCommentAndAttributes($param, $item); + } + $this->addCommentAndAttributes($function, $node); + if ($node->stmts) { + $function->setBody($this->getReformattedBody($node->stmts, 2)); + } + } + + + private function toPhp($value): string + { + return $this->printer->prettyPrint([$value]); + } + + private function getNodeContents(Node ...$nodes): string { $start = $nodes[0]->getStartFilePos(); diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 689050db..6f833457 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -279,6 +279,23 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property } + public function fromClassCode(string $code): ClassType + { + $classes = $this->fromCode($code)->getClasses(); + if (!$classes) { + throw new Nette\InvalidStateException('The code does not contain any class.'); + } + return reset($classes); + } + + + public function fromCode(string $code): PhpFile + { + $reader = new Extractor($code); + return $reader->extractAll(); + } + + private function getAttributes($from): array { if (PHP_VERSION_ID < 80000) { diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index c1c67bd7..ce3cd02c 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -32,6 +32,12 @@ final class PhpFile private $strictTypes = false; + public static function fromCode(string $code): self + { + return (new Factory)->fromCode($code); + } + + public function addClass(string $name): ClassType { return $this diff --git a/tests/PhpGenerator/ClassType.fromCode.phpt b/tests/PhpGenerator/ClassType.fromCode.phpt new file mode 100644 index 00000000..786d6ea8 --- /dev/null +++ b/tests/PhpGenerator/ClassType.fromCode.phpt @@ -0,0 +1,28 @@ +extractAll(); +Assert::type(Nette\PhpGenerator\PhpFile::class, $file); +sameFile(__DIR__ . '/expected/Factory.fromCode.expect', (string) $file); + +$file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.74.php')))->extractAll(); +sameFile(__DIR__ . '/expected/Factory.fromCode.74.expect', (string) $file); + +$file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.80.php')))->extractAll(); +sameFile(__DIR__ . '/expected/Factory.fromCode.80.expect', (string) $file); + +//$file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.81.php')))->extractAll(); +//sameFile(__DIR__ . '/expected/Factory.fromCode.81.expect', (string) $file); + +$file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/enum.php')))->extractAll(); +sameFile(__DIR__ . '/expected/Factory.fromCode.enum.expect', (string) $file); + +$file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/traits.php')))->extractAll(); +sameFile(__DIR__ . '/expected/Factory.fromCode.traits.expect', (string) $file); + +$file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/bodies.php')))->extractAll(); +sameFile(__DIR__ . '/expected/Factory.fromCode.bodies.expect', (string) $file); + +$file = (new Extractor( + <<<'XX' +extractAll(); +Assert::type(Nette\PhpGenerator\PhpFile::class, $file); +Assert::match(<<<'XX' +fromClassCode(file_get_contents(__DIR__ . '/fixtures/classes.php')); +Assert::type(Nette\PhpGenerator\ClassType::class, $class); +Assert::match(<<<'XX' +/** + * Interface + * @author John Doe + */ +interface Interface1 +{ + function func1(); +} +XX +, (string) $class); diff --git a/tests/PhpGenerator/PhpFile.phpt b/tests/PhpGenerator/PhpFile.phpt index a6e245e0..270277df 100644 --- a/tests/PhpGenerator/PhpFile.phpt +++ b/tests/PhpGenerator/PhpFile.phpt @@ -128,3 +128,9 @@ $file->setStrictTypes(); $file->addClass('A'); sameFile(__DIR__ . '/expected/PhpFile.strictTypes.expect', (string) $file); + + + +$file = PhpFile::fromCode(file_get_contents(__DIR__ . '/fixtures/classes.php')); +Assert::type(PhpFile::class, $file); +sameFile(__DIR__ . '/expected/Factory.fromCode.expect', (string) $file); diff --git a/tests/PhpGenerator/expected/Factory.fromCode.74.expect b/tests/PhpGenerator/expected/Factory.fromCode.74.expect new file mode 100644 index 00000000..2b4c373c --- /dev/null +++ b/tests/PhpGenerator/expected/Factory.fromCode.74.expect @@ -0,0 +1,13 @@ +methods[$member->getName()] = $member; + */ + throw new \Nette\InvalidArgumentException('Argument must be Method|Property|Constant.'); + } + + + public function complex() + { + echo 1; + // single line comment + + // spaces - indent + // spaces - indent + + /* multi + line + comment */ + if ( + $a + && $b + $c) + {} + + /** multi + line + comment */ + // Alias Method will not be resolved in comment + if ($member instanceof \Abc\Method) { + $s1 = "\na\n\tb\n\t\tc\n"; + $s2 = "\na\n\t{$b}\n\t\t$c\n"; + + $s3 = "a\n\t{$b}\n\t\t$c" + ; + $s3 = "a\n\tb\n\t\tc" + ; + // inline HTML is not supported + ?> + a + b + c + Date: Sat, 18 Sep 2021 17:45:47 +0200 Subject: [PATCH 033/266] PhpNamespace::unresolveName() prefers shorter names --- src/PhpGenerator/PhpNamespace.php | 11 +++++------ tests/PhpGenerator/PhpNamespace.phpt | 5 ++++- tests/PhpGenerator/expected/PhpNamespace.expect | 1 + 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 40356e48..5a0b145b 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -141,8 +141,11 @@ public function unresolveName(string $name): string return $name; } $name = ltrim($name, '\\'); - $res = null; $lower = strtolower($name); + $res = Strings::startsWith($lower, strtolower($this->name) . '\\') + ? substr($name, strlen($this->name) + 1) + : null; + foreach ($this->uses as $alias => $original) { if (Strings::startsWith($lower . '\\', strtolower($original) . '\\')) { $short = $alias . substr($name, strlen($original)); @@ -152,11 +155,7 @@ public function unresolveName(string $name): string } } - if (!$res && Strings::startsWith($lower, strtolower($this->name) . '\\')) { - return substr($name, strlen($this->name) + 1); - } else { - return $res ?: ($this->name ? '\\' : '') . $name; - } + return $res ?: ($this->name ? '\\' : '') . $name; } diff --git a/tests/PhpGenerator/PhpNamespace.phpt b/tests/PhpGenerator/PhpNamespace.phpt index 28ee045d..d5c865e6 100644 --- a/tests/PhpGenerator/PhpNamespace.phpt +++ b/tests/PhpGenerator/PhpNamespace.phpt @@ -42,8 +42,11 @@ Assert::same('?A', $namespace->unresolveType('?foo\A')); Assert::same('A&\Countable', $namespace->unresolveType('foo\A&Countable')); Assert::same('', $namespace->unresolveType('')); +$namespace->addUse('Foo'); +Assert::same('B', $namespace->unresolveName('Foo\B')); + $namespace->addUse('Bar\C'); -Assert::same(['C' => 'Bar\C'], $namespace->getUses()); +Assert::same(['C' => 'Bar\C', 'Foo' => 'Foo'], $namespace->getUses()); Assert::same('\Bar', $namespace->unresolveName('Bar')); Assert::same('C', $namespace->unresolveName('\bar\C')); diff --git a/tests/PhpGenerator/expected/PhpNamespace.expect b/tests/PhpGenerator/expected/PhpNamespace.expect index f42132d7..3e451749 100644 --- a/tests/PhpGenerator/expected/PhpNamespace.expect +++ b/tests/PhpGenerator/expected/PhpNamespace.expect @@ -1,6 +1,7 @@ namespace Foo; use Bar\C; +use Foo; #[A] class A implements A, C From a758e6f1d827478d5b1d55c87004d254d69c033d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 21 Sep 2021 13:34:55 +0200 Subject: [PATCH 034/266] Escapes */ in comments --- src/PhpGenerator/Helpers.php | 4 +++- tests/PhpGenerator/ClassType.phpt | 2 +- tests/PhpGenerator/expected/ClassType.expect | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index caad70b5..eec1da33 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -45,7 +45,9 @@ public static function formatArgs(string $statement, array $args): string public static function formatDocComment(string $content): string { - if (($s = trim($content)) === '') { + $s = trim($content); + $s = str_replace('*/', '* /', $s); + if ($s === '') { return ''; } elseif (strpos($content, "\n") === false) { return "/** $s */\n"; diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index 4ed36c42..01ab95ce 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -33,7 +33,7 @@ $class ->addImplement('IOne') ->setTraits(['ObjectTrait']) ->addTrait('AnotherTrait', ['sayHello as protected']) - ->addComment("Description of class.\nThis is example\n") + ->addComment("Description of class.\nThis is example\n /**/") ->addComment('@property-read Nette\Forms\Form $form') ->setConstants(['ROLE' => 'admin']) ->addConstant('ACTIVE', false) diff --git a/tests/PhpGenerator/expected/ClassType.expect b/tests/PhpGenerator/expected/ClassType.expect index 090aeaf9..ba2f6e95 100644 --- a/tests/PhpGenerator/expected/ClassType.expect +++ b/tests/PhpGenerator/expected/ClassType.expect @@ -1,7 +1,7 @@ /** * Description of class. * This is example - * + * /** / * @property-read Nette\Forms\Form $form */ abstract class Example extends ParentClass implements IExample, IOne From c5181de847125cd727cca3a78a120736598f717f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 20 Sep 2021 12:17:48 +0200 Subject: [PATCH 035/266] improved validation error messages --- src/PhpGenerator/ClassType.php | 10 +++++----- src/PhpGenerator/Method.php | 2 +- tests/PhpGenerator/ClassType.validate.phpt | 4 ++-- tests/PhpGenerator/Method.validate.phpt | 6 +++--- tests/PhpGenerator/Printer.phpt | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 15a7060d..45a905ea 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -600,14 +600,14 @@ public function hasMethod(string $name): bool /** @throws Nette\InvalidStateException */ public function validate(): void { - if ($this->abstract && $this->final) { - throw new Nette\InvalidStateException('Class cannot be abstract and final.'); - - } elseif ($this->isEnum() && ($this->abstract || $this->final || $this->extends || $this->properties)) { - throw new Nette\InvalidStateException('Enum cannot be abstract or final or extends class or have properties.'); + if ($this->isEnum() && ($this->abstract || $this->final || $this->extends || $this->properties)) { + throw new Nette\InvalidStateException("Enum '$this->name' cannot be abstract or final or extends class or have properties."); } elseif (!$this->name && ($this->abstract || $this->final)) { throw new Nette\InvalidStateException('Anonymous class cannot be abstract or final.'); + + } elseif ($this->abstract && $this->final) { + throw new Nette\InvalidStateException("Class '$this->name' cannot be abstract and final at the same time."); } } diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index 0eab60d1..fd0a151d 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -137,7 +137,7 @@ public function addPromotedParameter(string $name, $defaultValue = null): Promot public function validate(): void { if ($this->abstract && ($this->final || $this->visibility === ClassType::VISIBILITY_PRIVATE)) { - throw new Nette\InvalidStateException('Method cannot be abstract and final or private.'); + throw new Nette\InvalidStateException("Method $this->name() cannot be abstract and final or private at the same time."); } } } diff --git a/tests/PhpGenerator/ClassType.validate.phpt b/tests/PhpGenerator/ClassType.validate.phpt index 4dbadf99..3ff96b7d 100644 --- a/tests/PhpGenerator/ClassType.validate.phpt +++ b/tests/PhpGenerator/ClassType.validate.phpt @@ -13,13 +13,13 @@ Assert::exception(function () { $class = new ClassType('A'); $class->setFinal(true)->setAbstract(true); $class->validate(); -}, Nette\InvalidStateException::class, 'Class cannot be abstract and final.'); +}, Nette\InvalidStateException::class, "Class 'A' cannot be abstract and final at the same time."); Assert::exception(function () { $class = new ClassType('A'); $class->setAbstract(true)->setFinal(true); $class->validate(); -}, Nette\InvalidStateException::class, 'Class cannot be abstract and final.'); +}, Nette\InvalidStateException::class, "Class 'A' cannot be abstract and final at the same time."); Assert::exception(function () { $class = new ClassType; diff --git a/tests/PhpGenerator/Method.validate.phpt b/tests/PhpGenerator/Method.validate.phpt index 7b4342f7..ce9ca61e 100644 --- a/tests/PhpGenerator/Method.validate.phpt +++ b/tests/PhpGenerator/Method.validate.phpt @@ -13,16 +13,16 @@ Assert::exception(function () { $method = new Method('foo'); $method->setFinal(true)->setAbstract(true); $method->validate(); -}, Nette\InvalidStateException::class, 'Method cannot be abstract and final or private.'); +}, Nette\InvalidStateException::class, 'Method foo() cannot be abstract and final or private at the same time.'); Assert::exception(function () { $method = new Method('foo'); $method->setAbstract(true)->setFinal(true); $method->validate(); -}, Nette\InvalidStateException::class, 'Method cannot be abstract and final or private.'); +}, Nette\InvalidStateException::class, 'Method foo() cannot be abstract and final or private at the same time.'); Assert::exception(function () { $method = new Method('foo'); $method->setAbstract(true)->setVisibility('private'); $method->validate(); -}, Nette\InvalidStateException::class, 'Method cannot be abstract and final or private.'); +}, Nette\InvalidStateException::class, 'Method foo() cannot be abstract and final or private at the same time.'); diff --git a/tests/PhpGenerator/Printer.phpt b/tests/PhpGenerator/Printer.phpt index 11a58554..43972d4d 100644 --- a/tests/PhpGenerator/Printer.phpt +++ b/tests/PhpGenerator/Printer.phpt @@ -87,4 +87,4 @@ Assert::exception(function () { $class = new ClassType; $class->setFinal(true)->setAbstract(true); (new Printer)->printClass($class); -}, Nette\InvalidStateException::class, 'Class cannot be abstract and final.'); +}, Nette\InvalidStateException::class, 'Anonymous class cannot be abstract or final.'); From 0ba04ffc448fd2c21d8c0f4067233609ff3402b0 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 20 Sep 2021 12:04:32 +0200 Subject: [PATCH 036/266] added Property and Parameter validation --- src/PhpGenerator/Parameter.php | 5 +++++ src/PhpGenerator/Printer.php | 2 ++ src/PhpGenerator/PromotedParameter.php | 11 +++++++++++ src/PhpGenerator/Property.php | 9 +++++++++ 4 files changed, 27 insertions(+) diff --git a/src/PhpGenerator/Parameter.php b/src/PhpGenerator/Parameter.php index 167325d9..27bfbbdd 100644 --- a/src/PhpGenerator/Parameter.php +++ b/src/PhpGenerator/Parameter.php @@ -131,4 +131,9 @@ public function hasDefaultValue(): bool { return $this->hasDefaultValue; } + + + public function validate(): void + { + } } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 23e79cd3..67bd8f6b 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -154,6 +154,7 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st $properties = []; foreach ($class->getProperties() as $property) { + $property->validate(); $type = $property->getType(); $def = (($property->getVisibility() ?: 'public') . ($property->isStatic() ? ' static' : '') @@ -289,6 +290,7 @@ public function printParameters($function, PhpNamespace $namespace = null, int $ $special = false; foreach ($list as $param) { + $param->validate(); $variadic = $function->isVariadic() && $param === end($list); $type = $param->getType(); $promoted = $param instanceof PromotedParameter ? $param : null; diff --git a/src/PhpGenerator/PromotedParameter.php b/src/PhpGenerator/PromotedParameter.php index e3316d09..00b8a817 100644 --- a/src/PhpGenerator/PromotedParameter.php +++ b/src/PhpGenerator/PromotedParameter.php @@ -9,6 +9,8 @@ namespace Nette\PhpGenerator; +use Nette; + /** * Promoted parameter in constructor. @@ -34,4 +36,13 @@ public function isReadOnly(): bool { return $this->readOnly; } + + + /** @throws Nette\InvalidStateException */ + public function validate(): void + { + if ($this->readOnly && !$this->getType()) { + throw new Nette\InvalidStateException("Property \${$this->getName()}: Read-only properties are only supported on typed property."); + } + } } diff --git a/src/PhpGenerator/Property.php b/src/PhpGenerator/Property.php index 25751196..656920df 100644 --- a/src/PhpGenerator/Property.php +++ b/src/PhpGenerator/Property.php @@ -131,4 +131,13 @@ public function isReadOnly(): bool { return $this->readOnly; } + + + /** @throws Nette\InvalidStateException */ + public function validate(): void + { + if ($this->readOnly && !$this->type) { + throw new Nette\InvalidStateException("Property \$$this->name: Read-only properties are only supported on typed property."); + } + } } From 7329ec26f897ef2e1fff9390b2083eb365d8894b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 23 Sep 2021 22:41:53 +0200 Subject: [PATCH 037/266] refactoring --- src/PhpGenerator/Helpers.php | 10 ++++++++++ src/PhpGenerator/PhpNamespace.php | 8 +------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index eec1da33..f73a9718 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -21,6 +21,16 @@ final class Helpers public const PHP_IDENT = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'; + public const KEYWORDS = [ + // built-in types + 'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1, + 'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, 'mixed' => 1, 'false' => 1, + 'never' => 1, + + // class keywords + 'self' => 1, 'parent' => 1, 'static' => 1, + ]; + /** @deprecated use (new Nette\PhpGenerator\Dumper)->dump() */ public static function dump($var): string diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 5a0b145b..cb6e7cc7 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -26,12 +26,6 @@ final class PhpNamespace { use Nette\SmartObject; - private const KEYWORDS = [ - 'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1, - 'callable' => 1, 'iterable' => 1, 'void' => 1, 'self' => 1, 'parent' => 1, 'static' => 1, - 'mixed' => 1, 'null' => 1, 'false' => 1, 'never' => 1, - ]; - /** @var string */ private $name; @@ -137,7 +131,7 @@ public function unresolveType(string $type): string public function unresolveName(string $name): string { - if (isset(self::KEYWORDS[strtolower($name)]) || $name === '') { + if (isset(Helpers::KEYWORDS[strtolower($name)]) || $name === '') { return $name; } $name = ltrim($name, '\\'); From e0741f4659c3f1a31084d781421642d41c875790 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 23 Sep 2021 20:30:39 +0200 Subject: [PATCH 038/266] added validator for types --- src/PhpGenerator/Helpers.php | 19 +++++++++++++++++++ src/PhpGenerator/Parameter.php | 11 +++-------- src/PhpGenerator/Property.php | 6 +----- src/PhpGenerator/Traits/FunctionLike.php | 6 +----- tests/PhpGenerator/invalidNames.phpt | 12 ++++++++++++ 5 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index f73a9718..a413ae50 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -117,4 +117,23 @@ public static function createObject(string $class, array $props) { return Dumper::createObject($class, $props); } + + + public static function validateType(?string $type, bool &$nullable): ?string + { + if ($type === '' || $type === null) { + return null; + } + if (!preg_match('#(?: + \?[\w\\\\]+| + [\w\\\\]+ (?: (&[\w\\\\]+)* | (\|[\w\\\\]+)* ) + )()$#xAD', $type)) { + throw new Nette\InvalidArgumentException("Value '$type' is not valid type."); + } + if ($type[0] === '?') { + $nullable = true; + return substr($type, 1); + } + return $type; + } } diff --git a/src/PhpGenerator/Parameter.php b/src/PhpGenerator/Parameter.php index 27bfbbdd..2d715275 100644 --- a/src/PhpGenerator/Parameter.php +++ b/src/PhpGenerator/Parameter.php @@ -56,11 +56,7 @@ public function isReference(): bool /** @return static */ public function setType(?string $type): self { - if ($type && $type[0] === '?') { - $type = substr($type, 1); - $this->nullable = true; - } - $this->type = $type; + $this->type = Helpers::validateType($type, $this->nullable); return $this; } @@ -74,15 +70,14 @@ public function getType(): ?string /** @deprecated use setType() */ public function setTypeHint(?string $type): self { - $this->type = $type; - return $this; + return $this->setType($type); } /** @deprecated use getType() */ public function getTypeHint(): ?string { - return $this->type; + return $this->getType(); } diff --git a/src/PhpGenerator/Property.php b/src/PhpGenerator/Property.php index 656920df..f5ed5abf 100644 --- a/src/PhpGenerator/Property.php +++ b/src/PhpGenerator/Property.php @@ -76,11 +76,7 @@ public function isStatic(): bool /** @return static */ public function setType(?string $type): self { - if ($type && $type[0] === '?') { - $type = substr($type, 1); - $this->nullable = true; - } - $this->type = $type; + $this->type = Helpers::validateType($type, $this->nullable); return $this; } diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index 7697c823..933ea91e 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -127,11 +127,7 @@ public function isVariadic(): bool /** @return static */ public function setReturnType(?string $type): self { - if ($type && $type[0] === '?') { - $type = substr($type, 1); - $this->returnNullable = true; - } - $this->returnType = $type; + $this->returnType = Nette\PhpGenerator\Helpers::validateType($type, $this->returnNullable); return $this; } diff --git a/tests/PhpGenerator/invalidNames.phpt b/tests/PhpGenerator/invalidNames.phpt index d53179eb..281ec563 100644 --- a/tests/PhpGenerator/invalidNames.phpt +++ b/tests/PhpGenerator/invalidNames.phpt @@ -109,6 +109,10 @@ Assert::exception(function () { new Nette\PhpGenerator\Property('*'); }, Nette\InvalidArgumentException::class); +Assert::exception(function () { + (new Nette\PhpGenerator\Property('foo'))->setType('a b'); +}, Nette\InvalidArgumentException::class); + Assert::noError(function () { new Nette\PhpGenerator\Parameter('Iñtërnâtiônàlizætiøn'); @@ -130,6 +134,10 @@ Assert::exception(function () { new Nette\PhpGenerator\Parameter('$test'); }, Nette\InvalidArgumentException::class); +Assert::exception(function () { + (new Nette\PhpGenerator\Parameter('foo'))->setType('a b'); +}, Nette\InvalidArgumentException::class); + Assert::noError(function () { new Nette\PhpGenerator\Method('Iñtërnâtiônàlizætiøn'); @@ -147,6 +155,10 @@ Assert::exception(function () { new Nette\PhpGenerator\Method('*'); }, Nette\InvalidArgumentException::class); +Assert::exception(function () { + (new Nette\PhpGenerator\Method('foo'))->setReturnType('a b'); +}, Nette\InvalidArgumentException::class); + Assert::noError(function () { new Nette\PhpGenerator\GlobalFunction('Iñtërnâtiônàlizætiøn'); From 42884e1ebaa0c2789460db99500f1c9ed044a6e7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 23 Sep 2021 22:56:17 +0200 Subject: [PATCH 039/266] checks reserved class names --- src/PhpGenerator/ClassType.php | 2 +- src/PhpGenerator/Helpers.php | 16 ++++++++++++++++ tests/PhpGenerator/invalidNames.phpt | 8 ++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 45a905ea..2197d467 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -151,7 +151,7 @@ public function getNamespace(): ?PhpNamespace /** @return static */ public function setName(?string $name): self { - if ($name !== null && !Helpers::isIdentifier($name)) { + if ($name !== null && (!Helpers::isIdentifier($name) || isset(Helpers::KEYWORDS[strtolower($name)]))) { throw new Nette\InvalidArgumentException("Value '$name' is not valid class name."); } $this->name = $name; diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index a413ae50..449138fb 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -29,6 +29,22 @@ final class Helpers // class keywords 'self' => 1, 'parent' => 1, 'static' => 1, + + // PHP keywords + 'include' => 1, 'include_once' => 1, 'eval' => 1, 'require' => 1, 'require_once' => 1, 'or' => 1, 'xor' => 1, + 'and' => 1, 'instanceof' => 1, 'new' => 1, 'clone' => 1, 'exit' => 1, 'if' => 1, 'elseif' => 1, 'else' => 1, + 'endif' => 1, 'echo' => 1, 'do' => 1, 'while' => 1, 'endwhile' => 1, 'for' => 1, 'endfor' => 1, 'foreach' => 1, + 'endforeach' => 1, 'declare' => 1, 'enddeclare' => 1, 'as' => 1, 'try' => 1, 'catch' => 1, 'finally' => 1, + 'throw' => 1, 'use' => 1, 'insteadof' => 1, 'global' => 1, 'var' => 1, 'unset' => 1, 'isset' => 1, 'empty' => 1, + 'continue' => 1, 'goto' => 1, 'function' => 1, 'const' => 1, 'return' => 1, 'print' => 1, 'yield' => 1, 'list' => 1, + 'switch' => 1, 'endswitch' => 1, 'case' => 1, 'default' => 1, 'break' => 1, 'array' => 1, 'callable' => 1, + 'extends' => 1, 'implements' => 1, 'namespace' => 1, 'trait' => 1, 'interface' => 1, 'class' => 1, '__CLASS__' => 1, + '__TRAIT__' => 1, '__FUNCTION__' => 1, '__METHOD__' => 1, '__LINE__' => 1, '__FILE__' => 1, '__DIR__' => 1, + '__NAMESPACE__' => 1, 'fn' => 1, 'match' => 1, 'enum' => 1, 'static' => 1, 'abstract' => 1, 'final' => 1, + 'private' => 1, 'protected' => 1, 'public' => 1, 'readonly' => 1, + + // additional reserved class names + 'true' => 1, ]; diff --git a/tests/PhpGenerator/invalidNames.phpt b/tests/PhpGenerator/invalidNames.phpt index 281ec563..8b90d89c 100644 --- a/tests/PhpGenerator/invalidNames.phpt +++ b/tests/PhpGenerator/invalidNames.phpt @@ -62,6 +62,14 @@ Assert::exception(function () { new Nette\PhpGenerator\ClassType('\abc'); }, Nette\InvalidArgumentException::class); +Assert::exception(function () { + new Nette\PhpGenerator\ClassType('enum'); +}, Nette\InvalidArgumentException::class); + +Assert::exception(function () { + new Nette\PhpGenerator\ClassType('bool'); +}, Nette\InvalidArgumentException::class); + $class = new Nette\PhpGenerator\ClassType('Abc'); Assert::exception(function () use ($class) { From b05a3ae8cb5505d400b735505c2ca7ff610e2d58 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 23 Sep 2021 18:44:02 +0200 Subject: [PATCH 040/266] PhpNamespace::addUse() added validation --- src/PhpGenerator/PhpNamespace.php | 9 +++++++++ tests/PhpGenerator/invalidNames.phpt | 16 ++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index cb6e7cc7..ba07fb57 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -87,6 +87,15 @@ public function getBracketedSyntax(): bool */ public function addUse(string $name, string $alias = null, string &$aliasOut = null): self { + if ( + !Helpers::isNamespaceIdentifier($name, true) + || (Helpers::isIdentifier($name) && isset(Helpers::KEYWORDS[strtolower($name)])) + ) { + throw new Nette\InvalidArgumentException("Value '$name' is not valid class name."); + } elseif ($alias && (!Helpers::isIdentifier($alias) || isset(Helpers::KEYWORDS[strtolower($alias)]))) { + throw new Nette\InvalidArgumentException("Value '$alias' is not valid alias."); + } + $name = ltrim($name, '\\'); if ($alias === null && $this->name === Helpers::extractNamespace($name)) { $alias = Helpers::extractShortName($name); diff --git a/tests/PhpGenerator/invalidNames.phpt b/tests/PhpGenerator/invalidNames.phpt index 8b90d89c..b0e87523 100644 --- a/tests/PhpGenerator/invalidNames.phpt +++ b/tests/PhpGenerator/invalidNames.phpt @@ -36,6 +36,22 @@ Assert::exception(function () { new Nette\PhpGenerator\PhpNamespace('\abc'); }, Nette\InvalidArgumentException::class); +Assert::exception(function () { + (new Nette\PhpGenerator\PhpNamespace('Abc'))->addUse(''); +}, Nette\InvalidArgumentException::class); + +Assert::exception(function () { + (new Nette\PhpGenerator\PhpNamespace('Abc'))->addUse('Foo', 'a b'); +}, Nette\InvalidArgumentException::class); + +Assert::exception(function () { + (new Nette\PhpGenerator\PhpNamespace('Abc'))->addUse('true'); +}, Nette\InvalidArgumentException::class); + +Assert::exception(function () { + (new Nette\PhpGenerator\PhpNamespace('Abc'))->addUse('aaa', 'true'); +}, Nette\InvalidArgumentException::class); + Assert::noError(function () { new Nette\PhpGenerator\ClassType(null); // anonymous class From 0976f794d4d8154d6543294e0fbfa32c3a1c1dfe Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 23 Sep 2021 17:25:26 +0200 Subject: [PATCH 041/266] Revert "Dumper: dumps Closure as PHP code" (BC break) Because there is no option to use your own printer This reverts commit 59bb35ed6e8da95854fbf7b7d47dce6156b42915. --- src/PhpGenerator/Dumper.php | 6 +++--- tests/PhpGenerator/Dumper.dump().phpt | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 9ebbc06b..de78f773 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -46,10 +46,10 @@ private function dumpVar(&$var, array $parents = [], int $level = 0, int $column } elseif (is_array($var)) { return $this->dumpArray($var, $parents, $level, $column); + } elseif ($var instanceof Literal) { + return ltrim(Nette\Utils\Strings::indent(trim((string) $var), $level), "\t"); + } elseif (is_object($var)) { - if ($var instanceof Literal || $var instanceof Closure) { - return ltrim(Nette\Utils\Strings::indent(trim((string) $var), $level), "\t"); - } return $this->dumpObject($var, $parents, $level); } elseif (is_resource($var)) { diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index 837f7f29..fbcc62db 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -45,11 +45,6 @@ Assert::same('\'He\ll\\\\\o \\\'wor\\\\\\\'ld\\\\\'', $dumper->dump('He\ll\\\o \ // literal Assert::same('[$s]', $dumper->dump([new Literal('$s')])); Assert::same("[strlen('hello')]", $dumper->dump([new Literal('strlen(?)', ['hello'])])); -same('[ - function () { - return 1; - }, -]', $dumper->dump([(new Nette\PhpGenerator\Closure)->setBody('return 1;')])); // arrays From 0194e2d92f4a4c2ac812b580bf55da6ae8f2e132 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 23 Sep 2021 15:58:13 +0200 Subject: [PATCH 042/266] Printer: refactoring, added property $namespace --- src/PhpGenerator/Printer.php | 69 ++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 67bd8f6b..d511dbed 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -32,28 +32,33 @@ class Printer /** @var string */ protected $returnTypeColon = ': '; + /** @var ?PhpNamespace */ + protected $namespace; + /** @var bool */ private $resolveTypes = true; public function printFunction(GlobalFunction $function, PhpNamespace $namespace = null): string { + $this->namespace = $this->resolveTypes ? $namespace : null; $line = 'function ' . ($function->getReturnReference() ? '&' : '') . $function->getName(); - $returnType = $this->printReturnType($function, $namespace); + $returnType = $this->printReturnType($function); return Helpers::formatDocComment($function->getComment() . "\n") - . self::printAttributes($function->getAttributes(), $namespace) + . self::printAttributes($function->getAttributes()) . $line - . $this->printParameters($function, $namespace, strlen($line) + strlen($returnType) + 2) // 2 = parentheses + . $this->printParameters($function, strlen($line) + strlen($returnType) + 2) // 2 = parentheses . $returnType . "\n{\n" . $this->indent(ltrim(rtrim($function->getBody()) . "\n")) . "}\n"; } - public function printClosure(Closure $closure): string + public function printClosure(Closure $closure, PhpNamespace $namespace = null): string { + $this->namespace = $this->resolveTypes ? $namespace : null; $uses = []; foreach ($closure->getUses() as $param) { $uses[] = ($param->isReference() ? '&' : '') . '$' . $param->getName(); @@ -62,35 +67,37 @@ public function printClosure(Closure $closure): string ? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . "\n" : $tmp; - return self::printAttributes($closure->getAttributes(), null, true) + return self::printAttributes($closure->getAttributes(), true) . 'function ' . ($closure->getReturnReference() ? '&' : '') - . $this->printParameters($closure, null) + . $this->printParameters($closure) . ($uses ? " use ($useStr)" : '') - . $this->printReturnType($closure, null) + . $this->printReturnType($closure) . " {\n" . $this->indent(ltrim(rtrim($closure->getBody()) . "\n")) . '}'; } - public function printArrowFunction(Closure $closure): string + public function printArrowFunction(Closure $closure, PhpNamespace $namespace = null): string { + $this->namespace = $this->resolveTypes ? $namespace : null; foreach ($closure->getUses() as $use) { if ($use->isReference()) { throw new Nette\InvalidArgumentException('Arrow function cannot bind variables by-reference.'); } } - return self::printAttributes($closure->getAttributes(), null) + return self::printAttributes($closure->getAttributes()) . 'fn' . ($closure->getReturnReference() ? '&' : '') - . $this->printParameters($closure, null) - . $this->printReturnType($closure, null) + . $this->printParameters($closure) + . $this->printReturnType($closure) . ' => ' . trim($closure->getBody()) . ';'; } public function printMethod(Method $method, PhpNamespace $namespace = null): string { + $this->namespace = $this->resolveTypes ? $namespace : null; $method->validate(); $line = ($method->isAbstract() ? 'abstract ' : '') . ($method->isFinal() ? 'final ' : '') @@ -99,12 +106,12 @@ public function printMethod(Method $method, PhpNamespace $namespace = null): str . 'function ' . ($method->getReturnReference() ? '&' : '') . $method->getName(); - $returnType = $this->printReturnType($method, $namespace); + $returnType = $this->printReturnType($method); return Helpers::formatDocComment($method->getComment() . "\n") - . self::printAttributes($method->getAttributes(), $namespace) + . self::printAttributes($method->getAttributes()) . $line - . ($params = $this->printParameters($method, $namespace, strlen($line) + strlen($returnType) + strlen($this->indentation) + 2)) // 2 = parentheses + . ($params = $this->printParameters($method, strlen($line) + strlen($returnType) + strlen($this->indentation) + 2)) // 2 = parentheses . $returnType . ($method->isAbstract() || $method->getBody() === null ? ";\n" @@ -117,8 +124,9 @@ public function printMethod(Method $method, PhpNamespace $namespace = null): str public function printClass(ClassType $class, PhpNamespace $namespace = null): string { + $this->namespace = $this->resolveTypes ? $namespace : null; $class->validate(); - $resolver = $this->resolveTypes && $namespace + $resolver = $this->namespace ? [$namespace, 'unresolveType'] : function ($s) { return $s; }; @@ -131,7 +139,7 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st $cases = []; foreach ($class->getCases() as $case) { $cases[] = Helpers::formatDocComment((string) $case->getComment()) - . self::printAttributes($case->getAttributes(), $namespace) + . self::printAttributes($case->getAttributes()) . 'case ' . $case->getName() . ($case->getValue() === null ? '' : ' = ' . $this->dump($case->getValue())) . ";\n"; @@ -147,7 +155,7 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st . 'const ' . $const->getName() . ' = '; $consts[] = Helpers::formatDocComment((string) $const->getComment()) - . self::printAttributes($const->getAttributes(), $namespace) + . self::printAttributes($const->getAttributes()) . $def . $this->dump($const->getValue(), strlen($def)) . ";\n"; } @@ -160,11 +168,11 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st . ($property->isStatic() ? ' static' : '') . ($property->isReadOnly() && $type ? ' readonly' : '') . ' ' - . ltrim($this->printType($type, $property->isNullable(), $namespace) . ' ') + . ltrim($this->printType($type, $property->isNullable()) . ' ') . '$' . $property->getName()); $properties[] = Helpers::formatDocComment((string) $property->getComment()) - . self::printAttributes($property->getAttributes(), $namespace) + . self::printAttributes($property->getAttributes()) . $def . ($property->getValue() === null && !$property->isInitialized() ? '' : ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = ' . ";\n"; @@ -186,7 +194,7 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st return Strings::normalize( Helpers::formatDocComment($class->getComment() . "\n") - . self::printAttributes($class->getAttributes(), $namespace) + . self::printAttributes($class->getAttributes()) . ($class->isAbstract() ? 'abstract ' : '') . ($class->isFinal() ? 'final ' : '') . ($class->getName() ? $class->getType() . ' ' . $class->getName() . $enumType . ' ' : '') @@ -201,6 +209,7 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st public function printNamespace(PhpNamespace $namespace): string { + $this->namespace = $this->resolveTypes ? $namespace : null; $name = $namespace->getName(); $uses = $this->printUses($namespace); @@ -283,7 +292,7 @@ protected function printUses(PhpNamespace $namespace): string /** * @param Closure|GlobalFunction|Method $function */ - public function printParameters($function, PhpNamespace $namespace = null, int $column = 0): string + public function printParameters($function, int $column = 0): string { $params = []; $list = $function->getParameters(); @@ -296,12 +305,12 @@ public function printParameters($function, PhpNamespace $namespace = null, int $ $promoted = $param instanceof PromotedParameter ? $param : null; $params[] = ($promoted ? Helpers::formatDocComment((string) $promoted->getComment()) : '') - . ($attrs = self::printAttributes($param->getAttributes(), $namespace, true)) + . ($attrs = self::printAttributes($param->getAttributes(), true)) . ($promoted ? ($promoted->getVisibility() ?: 'public') . ($promoted->isReadOnly() && $type ? ' readonly' : '') . ' ' : '') - . ltrim($this->printType($type, $param->isNullable(), $namespace) . ' ') + . ltrim($this->printType($type, $param->isNullable()) . ' ') . ($param->isReference() ? '&' : '') . ($variadic ? '...' : '') . '$' . $param->getName() @@ -318,13 +327,13 @@ public function printParameters($function, PhpNamespace $namespace = null, int $ } - public function printType(?string $type, bool $nullable = false, PhpNamespace $namespace = null): string + public function printType(?string $type, bool $nullable): string { if ($type === null) { return ''; } - if ($this->resolveTypes && $namespace) { - $type = $namespace->unresolveType($type); + if ($this->namespace) { + $type = $this->namespace->unresolveType($type); } if ($nullable && strcasecmp($type, 'mixed')) { $type = strpos($type, '|') === false @@ -338,15 +347,15 @@ public function printType(?string $type, bool $nullable = false, PhpNamespace $n /** * @param Closure|GlobalFunction|Method $function */ - private function printReturnType($function, ?PhpNamespace $namespace): string + private function printReturnType($function): string { - return ($tmp = $this->printType($function->getReturnType(), $function->isReturnNullable(), $namespace)) + return ($tmp = $this->printType($function->getReturnType(), $function->isReturnNullable())) ? $this->returnTypeColon . $tmp : ''; } - private function printAttributes(array $attrs, ?PhpNamespace $namespace, bool $inline = false): string + private function printAttributes(array $attrs, bool $inline = false): string { if (!$attrs) { return ''; @@ -354,7 +363,7 @@ private function printAttributes(array $attrs, ?PhpNamespace $namespace, bool $i $items = []; foreach ($attrs as $attr) { $args = (new Dumper)->format('...?:', $attr->getArguments()); - $items[] = $this->printType($attr->getName(), false, $namespace) . ($args ? "($args)" : ''); + $items[] = $this->printType($attr->getName(), false) . ($args ? "($args)" : ''); } return $inline ? '#[' . implode(', ', $items) . '] ' From 31779e23fe607d5052ff9ec1917ad7f94cf31e15 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 23 Sep 2021 18:05:13 +0200 Subject: [PATCH 043/266] Printer: refactoring, added property $dumper --- src/PhpGenerator/Printer.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index d511dbed..882f8b8f 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -35,10 +35,19 @@ class Printer /** @var ?PhpNamespace */ protected $namespace; + /** @var ?Dumper */ + protected $dumper; + /** @var bool */ private $resolveTypes = true; + public function __construct() + { + $this->dumper = new Dumper; + } + + public function printFunction(GlobalFunction $function, PhpNamespace $namespace = null): string { $this->namespace = $this->resolveTypes ? $namespace : null; @@ -63,7 +72,7 @@ public function printClosure(Closure $closure, PhpNamespace $namespace = null): foreach ($closure->getUses() as $param) { $uses[] = ($param->isReference() ? '&' : '') . '$' . $param->getName(); } - $useStr = strlen($tmp = implode(', ', $uses)) > (new Dumper)->wrapLength && count($uses) > 1 + $useStr = strlen($tmp = implode(', ', $uses)) > $this->dumper->wrapLength && count($uses) > 1 ? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . "\n" : $tmp; @@ -270,7 +279,7 @@ protected function indent(string $s): string protected function dump($var, int $column = 0): string { - return (new Dumper)->dump($var, $column); + return $this->dumper->dump($var, $column); } @@ -321,7 +330,7 @@ public function printParameters($function, int $column = 0): string $line = implode(', ', $params); - return count($params) > 1 && ($special || strlen($line) + $column > (new Dumper)->wrapLength) + return count($params) > 1 && ($special || strlen($line) + $column > $this->dumper->wrapLength) ? "(\n" . $this->indent(implode(",\n", $params)) . ($special ? ',' : '') . "\n)" : "($line)"; } @@ -362,7 +371,7 @@ private function printAttributes(array $attrs, bool $inline = false): string } $items = []; foreach ($attrs as $attr) { - $args = (new Dumper)->format('...?:', $attr->getArguments()); + $args = $this->dumper->format('...?:', $attr->getArguments()); $items[] = $this->printType($attr->getName(), false) . ($args ? "($args)" : ''); } return $inline From 80d0c0b84129f1c108c7fb8e5e3766c0b2280ea9 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 23 Sep 2021 17:34:10 +0200 Subject: [PATCH 044/266] Dumper: refactoring --- src/PhpGenerator/Dumper.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index de78f773..1510ac04 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -47,8 +47,8 @@ private function dumpVar(&$var, array $parents = [], int $level = 0, int $column return $this->dumpArray($var, $parents, $level, $column); } elseif ($var instanceof Literal) { - return ltrim(Nette\Utils\Strings::indent(trim((string) $var), $level), "\t"); - + return $this->dumpLiteral($var, $level); + } elseif (is_object($var)) { return $this->dumpObject($var, $parents, $level); @@ -176,6 +176,13 @@ private function dumpObject(&$var, array $parents, int $level): string } + private function dumpLiteral(Literal $var, int $level): string + { + $s = Nette\Utils\Strings::indent(trim((string) $var), $level, "\t"); + return ltrim($s, "\t"); + } + + /** * Generates PHP statement. */ From 541170f037b8ec967947a36b6a7f6b7c6f1fca89 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 23 Sep 2021 15:05:00 +0200 Subject: [PATCH 045/266] Dumper: added $indentation --- src/PhpGenerator/Dumper.php | 18 +++++++++++------- src/PhpGenerator/Printer.php | 2 ++ tests/PhpGenerator/Dumper.dump().indent.phpt | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 1510ac04..9e0ee50b 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -25,6 +25,9 @@ final class Dumper /** @var int */ public $wrapLength = 120; + /** @var string */ + public $indentation = "\t"; + /** * Returns a PHP representation of a variable. @@ -92,7 +95,7 @@ private function dumpArray(array &$var, array $parents, int $level, int $column) throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.'); } - $space = str_repeat("\t", $level); + $space = str_repeat($this->indentation, $level); $outInline = ''; $outWrapped = "\n$space"; $parents[] = $var; @@ -106,7 +109,7 @@ private function dumpArray(array &$var, array $parents, int $level, int $column) $counter = is_int($k) ? max($k + 1, $counter) : $counter; $outInline .= ($outInline === '' ? '' : ', ') . $keyPart; $outInline .= $this->dumpVar($v, $parents, 0, $column + strlen($outInline)); - $outWrapped .= "\t" + $outWrapped .= $this->indentation . $keyPart . $this->dumpVar($v, $parents, $level + 1, strlen($keyPart)) . ",\n$space"; @@ -145,7 +148,7 @@ private function dumpObject(&$var, array $parents, int $level): string } $arr = (array) $var; - $space = str_repeat("\t", $level); + $space = str_repeat($this->indentation, $level); if ($level > $this->maxDepth || in_array($var, $parents ?? [], true)) { throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.'); @@ -161,7 +164,7 @@ private function dumpObject(&$var, array $parents, int $level): string foreach ($arr as $k => &$v) { if (!isset($props) || isset($props[$k])) { - $out .= "$space\t" + $out .= $space . $this->indentation . ($keyPart = $this->dumpVar($k) . ' => ') . $this->dumpVar($v, $parents, $level + 1, strlen($keyPart)) . ",\n"; @@ -178,8 +181,8 @@ private function dumpObject(&$var, array $parents, int $level): string private function dumpLiteral(Literal $var, int $level): string { - $s = Nette\Utils\Strings::indent(trim((string) $var), $level, "\t"); - return ltrim($s, "\t"); + $s = Nette\Utils\Strings::indent(trim((string) $var), $level, $this->indentation); + return ltrim($s, $this->indentation); } @@ -229,7 +232,8 @@ private function dumpArguments(array &$var, int $column, bool $named): string $k = !$named || is_int($k) ? '' : $k . ': '; $outInline .= $outInline === '' ? '' : ', '; $outInline .= $k . $this->dumpVar($v, [$var], 0, $column + strlen($outInline)); - $outWrapped .= ($outWrapped === '' ? '' : ',') . "\n\t" . $k . $this->dumpVar($v, [$var], 1); + $outWrapped .= ($outWrapped === '' ? '' : ',') . "\n" + . $this->indentation . $k . $this->dumpVar($v, [$var], 1); } return count($var) > 1 && (strpos($outInline, "\n") !== false || $column + strlen($outInline) > $this->wrapLength) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 882f8b8f..d6649b93 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -279,6 +279,7 @@ protected function indent(string $s): string protected function dump($var, int $column = 0): string { + $this->dumper->indentation = $this->indentation; return $this->dumper->dump($var, $column); } @@ -369,6 +370,7 @@ private function printAttributes(array $attrs, bool $inline = false): string if (!$attrs) { return ''; } + $this->dumper->indentation = $this->indentation; $items = []; foreach ($attrs as $attr) { $args = $this->dumper->format('...?:', $attr->getArguments()); diff --git a/tests/PhpGenerator/Dumper.dump().indent.phpt b/tests/PhpGenerator/Dumper.dump().indent.phpt index f01c4973..1f359af7 100644 --- a/tests/PhpGenerator/Dumper.dump().indent.phpt +++ b/tests/PhpGenerator/Dumper.dump().indent.phpt @@ -7,6 +7,7 @@ declare(strict_types=1); use Nette\PhpGenerator\Dumper; +use Nette\PhpGenerator\Literal; use Tester\Assert; @@ -38,3 +39,21 @@ same('[ 2, 3, ]', $dumper->dump([8 => 1, 2, 3], $dumper->wrapLength - 13)); + + +$dumper = new Dumper; +$dumper->indentation = ' '; +same('[ + 1, + 2, + 3, +]', $dumper->dump([1, 2, 3], $dumper->wrapLength - 8)); + +same( + "[ + 'multi' => [ + 1, + ], +]", + $dumper->dump(['multi' => new Literal("[\n1,\n]\n")]) +); From 8a3d6cb883b7ca3ce3302e7c8825ed7fc3358182 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 23 Sep 2021 17:39:25 +0200 Subject: [PATCH 046/266] Literal: added formatWith() for custom dumper --- src/PhpGenerator/Dumper.php | 3 ++- src/PhpGenerator/Literal.php | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 9e0ee50b..a45e7be5 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -181,7 +181,8 @@ private function dumpObject(&$var, array $parents, int $level): string private function dumpLiteral(Literal $var, int $level): string { - $s = Nette\Utils\Strings::indent(trim((string) $var), $level, $this->indentation); + $s = $var->formatWith($this); + $s = Nette\Utils\Strings::indent(trim($s), $level, $this->indentation); return ltrim($s, $this->indentation); } diff --git a/src/PhpGenerator/Literal.php b/src/PhpGenerator/Literal.php index 5e66987a..fb180b4f 100644 --- a/src/PhpGenerator/Literal.php +++ b/src/PhpGenerator/Literal.php @@ -18,17 +18,28 @@ class Literal /** @var string */ private $value; + /** @var ?array */ + private $args; + public function __construct(string $value, array $args = null) { - $this->value = $args === null - ? $value - : (new Dumper)->format($value, ...$args); + $this->value = $value; + $this->args = $args; } public function __toString(): string { - return $this->value; + return $this->formatWith(new Dumper); + } + + + /** @internal */ + public function formatWith(Dumper $dumper): string + { + return $this->args === null + ? $this->value + : $dumper->format($this->value, ...$this->args); } } From 018ed92f38174fed1789b692b03790ad5b889549 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 21 Sep 2021 13:43:26 +0200 Subject: [PATCH 047/266] Dumper::format() placeholder ? must not be followed by \w --- src/PhpGenerator/Dumper.php | 4 ++-- tests/PhpGenerator/Dumper.format().phpt | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index a45e7be5..319ca20e 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -192,12 +192,12 @@ private function dumpLiteral(Literal $var, int $level): string */ public function format(string $statement, ...$args): string { - $tokens = preg_split('#(\.\.\.\?:?|\$\?|->\?|::\?|\\\\\?|\?\*|\?)#', $statement, -1, PREG_SPLIT_DELIM_CAPTURE); + $tokens = preg_split('#(\.\.\.\?:?|\$\?|->\?|::\?|\\\\\?|\?\*|\?(?!\w))#', $statement, -1, PREG_SPLIT_DELIM_CAPTURE); $res = ''; foreach ($tokens as $n => $token) { if ($n % 2 === 0) { $res .= $token; - } elseif ($token === '\\?') { + } elseif ($token === '\?') { $res .= '?'; } elseif (!$args) { throw new Nette\InvalidArgumentException('Insufficient number of arguments.'); diff --git a/tests/PhpGenerator/Dumper.format().phpt b/tests/PhpGenerator/Dumper.format().phpt index 2ab3df99..c3cde14b 100644 --- a/tests/PhpGenerator/Dumper.format().phpt +++ b/tests/PhpGenerator/Dumper.format().phpt @@ -16,6 +16,8 @@ require __DIR__ . '/../bootstrap.php'; $dumper = new Dumper; Assert::same('func', $dumper->format('func')); Assert::same('func(1)', $dumper->format('func(?)', 1)); +Assert::same('fn(?string $x = 1)', $dumper->format('fn(?string $x = ?)', 1)); +Assert::same('fn(?string $x = 1)', $dumper->format('fn(\?string $x = ?)', 1)); Assert::same('func(1 ? 2 : 3)', $dumper->format('func(1 \? 2 : 3)')); Assert::same('func([1, 2])', $dumper->format('func(?)', [1, 2])); Assert::same('func(1, 2)', $dumper->format('func(...?)', [1, 2])); From 68233f6f965c23d22d4a55cf8961dcfde102ba40 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 22 Sep 2021 00:36:35 +0200 Subject: [PATCH 048/266] Helpers::unindent() fixed when level = 0 --- src/PhpGenerator/Helpers.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 449138fb..53a050dd 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -91,7 +91,9 @@ public static function unformatDocComment(string $comment): string public static function unindent(string $s, int $level = 1): string { - return preg_replace('#^(\t|\ \ \ \ ){1,' . $level . '}#m', '', $s); + return $level + ? preg_replace('#^(\t|\ \ \ \ ){1,' . $level . '}#m', '', $s) + : $s; } From 224209aac76790610083ef05ff0ccfea80ebc167 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 21 Sep 2021 23:47:28 +0200 Subject: [PATCH 049/266] PhpNamespace::unresolveName() renamed to simplifyName() --- readme.md | 2 +- src/PhpGenerator/PhpNamespace.php | 13 ++++++--- src/PhpGenerator/Printer.php | 4 +-- tests/PhpGenerator/PhpNamespace.phpt | 40 ++++++++++++++-------------- 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/readme.md b/readme.md index 6940f359..927281f1 100644 --- a/readme.md +++ b/readme.md @@ -600,7 +600,7 @@ $class->addImplement('Foo\A') // it will resolve to A ->addTrait('Bar\AliasedClass'); // it will resolve to AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->unresolveName('Foo\D')); // in comments resolve manually +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // in comments resolve manually $method->addParameter('arg') ->setType('Bar\OtherClass'); // it will resolve to \Bar\OtherClass diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index ba07fb57..9d593f9f 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -132,13 +132,20 @@ public function getUses(): array } - public function unresolveType(string $type): string + /** @deprecated use simplifyName() */ + public function unresolveName(string $name): string { - return preg_replace_callback('~[^|&?]+~', function ($m) { return $this->unresolveName($m[0]); }, $type); + return $this->simplifyName($name); } - public function unresolveName(string $name): string + public function simplifyType(string $type): string + { + return preg_replace_callback('~[^|&?]+~', function ($m) { return $this->simplifyName($m[0]); }, $type); + } + + + public function simplifyName(string $name): string { if (isset(Helpers::KEYWORDS[strtolower($name)]) || $name === '') { return $name; diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index d6649b93..1e47dab9 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -136,7 +136,7 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st $this->namespace = $this->resolveTypes ? $namespace : null; $class->validate(); $resolver = $this->namespace - ? [$namespace, 'unresolveType'] + ? [$namespace, 'simplifyType'] : function ($s) { return $s; }; $traits = []; @@ -343,7 +343,7 @@ public function printType(?string $type, bool $nullable): string return ''; } if ($this->namespace) { - $type = $this->namespace->unresolveType($type); + $type = $this->namespace->simplifyType($type); } if ($nullable && strcasecmp($type, 'mixed')) { $type = strpos($type, '|') === false diff --git a/tests/PhpGenerator/PhpNamespace.phpt b/tests/PhpGenerator/PhpNamespace.phpt index d5c865e6..fab55249 100644 --- a/tests/PhpGenerator/PhpNamespace.phpt +++ b/tests/PhpGenerator/PhpNamespace.phpt @@ -15,46 +15,46 @@ require __DIR__ . '/../bootstrap.php'; $namespace = new PhpNamespace(''); Assert::same('', $namespace->getName()); -Assert::same('A', $namespace->unresolveName('A')); -Assert::same('foo\A', $namespace->unresolveName('foo\A')); +Assert::same('A', $namespace->simplifyName('A')); +Assert::same('foo\A', $namespace->simplifyName('foo\A')); $namespace->addUse('Bar\C'); -Assert::same('Bar', $namespace->unresolveName('Bar')); -Assert::same('C', $namespace->unresolveName('bar\C')); -Assert::same('C\D', $namespace->unresolveName('Bar\C\D')); +Assert::same('Bar', $namespace->simplifyName('Bar')); +Assert::same('C', $namespace->simplifyName('bar\C')); +Assert::same('C\D', $namespace->simplifyName('Bar\C\D')); foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { - Assert::same($type, $namespace->unresolveName($type)); + Assert::same($type, $namespace->simplifyName($type)); } $namespace = new PhpNamespace('Foo'); Assert::same('Foo', $namespace->getName()); -Assert::same('\A', $namespace->unresolveName('\A')); -Assert::same('\A', $namespace->unresolveName('A')); -Assert::same('A', $namespace->unresolveName('foo\A')); +Assert::same('\A', $namespace->simplifyName('\A')); +Assert::same('\A', $namespace->simplifyName('A')); +Assert::same('A', $namespace->simplifyName('foo\A')); -Assert::same('A', $namespace->unresolveType('foo\A')); -Assert::same('null|A', $namespace->unresolveType('null|foo\A')); -Assert::same('?A', $namespace->unresolveType('?foo\A')); -Assert::same('A&\Countable', $namespace->unresolveType('foo\A&Countable')); -Assert::same('', $namespace->unresolveType('')); +Assert::same('A', $namespace->simplifyType('foo\A')); +Assert::same('null|A', $namespace->simplifyType('null|foo\A')); +Assert::same('?A', $namespace->simplifyType('?foo\A')); +Assert::same('A&\Countable', $namespace->simplifyType('foo\A&Countable')); +Assert::same('', $namespace->simplifyType('')); $namespace->addUse('Foo'); -Assert::same('B', $namespace->unresolveName('Foo\B')); +Assert::same('B', $namespace->simplifyName('Foo\B')); $namespace->addUse('Bar\C'); Assert::same(['C' => 'Bar\C', 'Foo' => 'Foo'], $namespace->getUses()); -Assert::same('\Bar', $namespace->unresolveName('Bar')); -Assert::same('C', $namespace->unresolveName('\bar\C')); -Assert::same('C', $namespace->unresolveName('bar\C')); -Assert::same('C\D', $namespace->unresolveName('Bar\C\D')); +Assert::same('\Bar', $namespace->simplifyName('Bar')); +Assert::same('C', $namespace->simplifyName('\bar\C')); +Assert::same('C', $namespace->simplifyName('bar\C')); +Assert::same('C\D', $namespace->simplifyName('Bar\C\D')); foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { - Assert::same($type, $namespace->unresolveName($type)); + Assert::same($type, $namespace->simplifyName($type)); } From 7971f456f03f16276262198663746b79de4ef69e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 21 Sep 2021 11:43:50 +0200 Subject: [PATCH 050/266] PhpNamespace::simplifyType() supports generics like Foo --- src/PhpGenerator/PhpNamespace.php | 2 +- tests/PhpGenerator/PhpNamespace.phpt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 9d593f9f..83c4e115 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -141,7 +141,7 @@ public function unresolveName(string $name): string public function simplifyType(string $type): string { - return preg_replace_callback('~[^|&?]+~', function ($m) { return $this->simplifyName($m[0]); }, $type); + return preg_replace_callback('~[\w\x7f-\xff\\\\]+~', function ($m) { return $this->simplifyName($m[0]); }, $type); } diff --git a/tests/PhpGenerator/PhpNamespace.phpt b/tests/PhpGenerator/PhpNamespace.phpt index fab55249..a86403c6 100644 --- a/tests/PhpGenerator/PhpNamespace.phpt +++ b/tests/PhpGenerator/PhpNamespace.phpt @@ -52,6 +52,8 @@ Assert::same('\Bar', $namespace->simplifyName('Bar')); Assert::same('C', $namespace->simplifyName('\bar\C')); Assert::same('C', $namespace->simplifyName('bar\C')); Assert::same('C\D', $namespace->simplifyName('Bar\C\D')); +Assert::same('A', $namespace->simplifyType('foo\A<\bar\C, Bar\C\D>')); +Assert::same('žluťoučký', $namespace->simplifyType('foo\žluťoučký')); foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { Assert::same($type, $namespace->simplifyName($type)); From 1cc5a68590145499479d2363024f172a009f504b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 21 Sep 2021 14:29:27 +0200 Subject: [PATCH 051/266] added TraitUse, represents use-statement --- readme.md | 5 +-- src/PhpGenerator/ClassType.php | 38 +++++++++++----- src/PhpGenerator/Printer.php | 5 ++- src/PhpGenerator/TraitUse.php | 47 ++++++++++++++++++++ tests/PhpGenerator/ClassType.addMember.phpt | 6 ++- tests/PhpGenerator/ClassType.phpt | 6 ++- tests/PhpGenerator/expected/ClassType.expect | 4 ++ tests/PhpGenerator/invalidNames.phpt | 4 +- 8 files changed, 94 insertions(+), 21 deletions(-) create mode 100644 src/PhpGenerator/TraitUse.php diff --git a/readme.md b/readme.md index 927281f1..b5d378a3 100644 --- a/readme.md +++ b/readme.md @@ -53,7 +53,6 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addTrait(Nette\SmartObject::class) ->addComment("Description of class.\nSecond line\n") ->addComment('@property-read Nette\Forms\Form $form'); @@ -72,7 +71,6 @@ It will render this result: */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -315,7 +313,8 @@ Using Traits ```php $class = new Nette\PhpGenerator\ClassType('Demo'); $class->addTrait('SmartObject'); -$class->addTrait('MyTrait', ['sayHello as protected']); +$class->addTrait('MyTrait', true) + ->addResolution('sayHello as protected'); echo $class; ``` diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 2197d467..7f644b18 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -56,7 +56,7 @@ final class ClassType /** @var string[] */ private $implements = []; - /** @var array[] */ + /** @var TraitUse[] */ private $traits = []; /** @var Constant[] name => Constant */ @@ -330,13 +330,18 @@ public function removeImplement(string $name): self /** - * @param string[] $names + * @param string[]|TraitUse[] $traits * @return static */ - public function setTraits(array $names): self + public function setTraits(array $traits): self { - $this->validateNames($names); - $this->traits = array_fill_keys($names, []); + $this->traits = []; + foreach ($traits as $trait) { + if (!$trait instanceof TraitUse) { + $trait = new TraitUse($trait); + } + $this->traits[$trait->getName()] = $trait; + } return $this; } @@ -355,11 +360,19 @@ public function getTraitResolutions(): array } - /** @return static */ - public function addTrait(string $name, array $resolutions = []): self + /** + * @param array|bool $resolutions + * @return static|TraitUse + */ + public function addTrait(string $name, $resolutions = []) { - $this->validateNames([$name]); - $this->traits[$name] = $resolutions; + $this->traits[$name] = $trait = new TraitUse($name); + if ($resolutions === true) { + return $trait; + } + array_map(function ($item) use ($trait) { + $trait->addResolution($item); + }, $resolutions); return $this; } @@ -373,7 +386,7 @@ public function removeTrait(string $name): self /** - * @param Method|Property|Constant|EnumCase $member + * @param Method|Property|Constant|EnumCase|TraitUse $member * @return static */ public function addMember($member): self @@ -393,8 +406,11 @@ public function addMember($member): self } elseif ($member instanceof EnumCase) { $this->cases[$member->getName()] = $member; + } elseif ($member instanceof TraitUse) { + $this->traits[$member->getName()] = $member; + } else { - throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant|EnumCase.'); + throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant|EnumCase|TraitUse.'); } return $this; diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 1e47dab9..f80fc4a0 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -140,8 +140,9 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st : function ($s) { return $s; }; $traits = []; - foreach ($class->getTraitResolutions() as $trait => $resolutions) { - $traits[] = 'use ' . $resolver($trait) + foreach ($class->getTraitResolutions() as $trait) { + $resolutions = $trait->getResolutions(); + $traits[] = 'use ' . $resolver($trait->getName()) . ($resolutions ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n" : ";\n"); } diff --git a/src/PhpGenerator/TraitUse.php b/src/PhpGenerator/TraitUse.php new file mode 100644 index 00000000..3c03480a --- /dev/null +++ b/src/PhpGenerator/TraitUse.php @@ -0,0 +1,47 @@ +name = $name; + } + + + public function addResolution(string $resolution): self + { + $this->resolutions[] = $resolution; + return $this; + } + + + public function getResolutions(): array + { + return $this->resolutions; + } +} diff --git a/tests/PhpGenerator/ClassType.addMember.phpt b/tests/PhpGenerator/ClassType.addMember.phpt index dab1611f..16e92350 100644 --- a/tests/PhpGenerator/ClassType.addMember.phpt +++ b/tests/PhpGenerator/ClassType.addMember.phpt @@ -12,17 +12,19 @@ require __DIR__ . '/../bootstrap.php'; Assert::exception(function () { (new ClassType('Example')) ->addMember(new stdClass); -}, Nette\InvalidArgumentException::class, 'Argument must be Method|Property|Constant|EnumCase.'); +}, Nette\InvalidArgumentException::class, 'Argument must be Method|Property|Constant|EnumCase|TraitUse.'); $class = (new ClassType('Example')) ->addMember($method = new Nette\PhpGenerator\Method('getHandle')) ->addMember($property = new Nette\PhpGenerator\Property('handle')) - ->addMember($const = new Nette\PhpGenerator\Constant('ROLE')); + ->addMember($const = new Nette\PhpGenerator\Constant('ROLE')) + ->addMember($trait = new Nette\PhpGenerator\TraitUse('Foo\Bar')); Assert::same(['getHandle' => $method], $class->getMethods()); Assert::same(['handle' => $property], $class->getProperties()); Assert::same(['ROLE' => $const], $class->getConstants()); +Assert::same(['Foo\Bar'], $class->getTraits()); Assert::same('', $method->getBody()); diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index 01ab95ce..b2ef1cf7 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -43,7 +43,6 @@ Assert::false($class->isFinal()); Assert::true($class->isAbstract()); Assert::same('ParentClass', $class->getExtends()); Assert::same(['ObjectTrait', 'AnotherTrait'], $class->getTraits()); -Assert::same(['ObjectTrait' => [], 'AnotherTrait' => ['sayHello as protected']], $class->getTraitResolutions()); Assert::count(2, $class->getConstants()); Assert::type(Nette\PhpGenerator\Constant::class, $class->getConstants()['ROLE']); @@ -139,6 +138,11 @@ $class->removeTrait('foo'); $class->addImplement('foo'); $class->removeImplement('foo'); +$class + ->addTrait('ThirdTrait', true) + ->addResolution('a as private foo') + ->addResolution('b as private bar'); + sameFile(__DIR__ . '/expected/ClassType.expect', (string) $class); diff --git a/tests/PhpGenerator/expected/ClassType.expect b/tests/PhpGenerator/expected/ClassType.expect index ba2f6e95..3a0143c2 100644 --- a/tests/PhpGenerator/expected/ClassType.expect +++ b/tests/PhpGenerator/expected/ClassType.expect @@ -10,6 +10,10 @@ abstract class Example extends ParentClass implements IExample, IOne use AnotherTrait { sayHello as protected; } + use ThirdTrait { + a as private foo; + b as private bar; + } const ROLE = 'admin'; final public const ACTIVE = false; diff --git a/tests/PhpGenerator/invalidNames.phpt b/tests/PhpGenerator/invalidNames.phpt index b0e87523..0ab21b42 100644 --- a/tests/PhpGenerator/invalidNames.phpt +++ b/tests/PhpGenerator/invalidNames.phpt @@ -110,11 +110,11 @@ Assert::exception(function () use ($class) { Assert::exception(function () use ($class) { $class->setTraits(['A', '*']); -}, Nette\InvalidArgumentException::class, "Value '*' is not valid class name."); +}, Nette\InvalidArgumentException::class, "Value '*' is not valid trait name."); Assert::exception(function () use ($class) { $class->addTrait('*'); -}, Nette\InvalidArgumentException::class, "Value '*' is not valid class name."); +}, Nette\InvalidArgumentException::class, "Value '*' is not valid trait name."); Assert::noError(function () { From 1170323b74bce46933f28c246fd13281bf782abc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 22 Sep 2021 01:20:53 +0200 Subject: [PATCH 052/266] "use Trait" can have comments [Closes #90] --- readme.md | 4 +++- src/PhpGenerator/Extractor.php | 11 +++++------ src/PhpGenerator/Printer.php | 3 ++- src/PhpGenerator/TraitUse.php | 1 + tests/PhpGenerator/ClassType.phpt | 3 ++- tests/PhpGenerator/expected/ClassType.expect | 1 + .../expected/Factory.fromCode.traits.expect | 5 +++-- tests/PhpGenerator/fixtures/traits.php | 1 + 8 files changed, 18 insertions(+), 11 deletions(-) diff --git a/readme.md b/readme.md index b5d378a3..583d09d2 100644 --- a/readme.md +++ b/readme.md @@ -314,7 +314,8 @@ Using Traits $class = new Nette\PhpGenerator\ClassType('Demo'); $class->addTrait('SmartObject'); $class->addTrait('MyTrait', true) - ->addResolution('sayHello as protected'); + ->addResolution('sayHello as protected') + ->addComment('@use MyTrait'); echo $class; ``` @@ -324,6 +325,7 @@ Result: class Demo { use SmartObject; + /** @use MyTrait */ use MyTrait { sayHello as protected; } diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 90eeaab3..ff4a02a3 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -291,14 +291,13 @@ private function addFunctionToFile(PhpFile $phpFile, Node\Stmt\Function_ $node): private function addTraitToClass(ClassType $class, Node\Stmt\TraitUse $node): void { - $res = []; - foreach ($node->adaptations as $item) { - $res[] = trim($this->toPhp($item), ';'); + foreach ($node->traits as $item) { + $trait = $class->addTrait($item->toString(), true); } - foreach ($node->traits as $trait) { - $class->addTrait($trait->toString(), $res); - $res = []; + foreach ($node->adaptations as $item) { + $trait->addResolution(trim($this->toPhp($item), ';')); } + $this->addCommentAndAttributes($trait, $node); } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index f80fc4a0..19d93b9c 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -142,7 +142,8 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st $traits = []; foreach ($class->getTraitResolutions() as $trait) { $resolutions = $trait->getResolutions(); - $traits[] = 'use ' . $resolver($trait->getName()) + $traits[] = Helpers::formatDocComment((string) $trait->getComment()) + . 'use ' . $resolver($trait->getName()) . ($resolutions ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n" : ";\n"); } diff --git a/src/PhpGenerator/TraitUse.php b/src/PhpGenerator/TraitUse.php index 3c03480a..4b8b457a 100644 --- a/src/PhpGenerator/TraitUse.php +++ b/src/PhpGenerator/TraitUse.php @@ -19,6 +19,7 @@ final class TraitUse { use Nette\SmartObject; use Traits\NameAware; + use Traits\CommentAware; /** @var array */ private $resolutions = []; diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index b2ef1cf7..625e9e53 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -141,7 +141,8 @@ $class->removeImplement('foo'); $class ->addTrait('ThirdTrait', true) ->addResolution('a as private foo') - ->addResolution('b as private bar'); + ->addResolution('b as private bar') + ->addComment('@use Foo'); sameFile(__DIR__ . '/expected/ClassType.expect', (string) $class); diff --git a/tests/PhpGenerator/expected/ClassType.expect b/tests/PhpGenerator/expected/ClassType.expect index 3a0143c2..a6373aa9 100644 --- a/tests/PhpGenerator/expected/ClassType.expect +++ b/tests/PhpGenerator/expected/ClassType.expect @@ -10,6 +10,7 @@ abstract class Example extends ParentClass implements IExample, IOne use AnotherTrait { sayHello as protected; } + /** @use Foo */ use ThirdTrait { a as private foo; b as private bar; diff --git a/tests/PhpGenerator/expected/Factory.fromCode.traits.expect b/tests/PhpGenerator/expected/Factory.fromCode.traits.expect index f47d6406..fa2e8c05 100644 --- a/tests/PhpGenerator/expected/Factory.fromCode.traits.expect +++ b/tests/PhpGenerator/expected/Factory.fromCode.traits.expect @@ -43,6 +43,7 @@ class ParentClass class Class1 extends ParentClass { + /** @use Foo */ use Trait2; } @@ -98,10 +99,10 @@ trait Trait1b class Class5 { - use Trait1 { + use Trait1; + use Trait1b { \Trait1b::f1 insteadof \Trait1; // not yet supported \Trait1b::f1 as private; } - use Trait1b; } diff --git a/tests/PhpGenerator/fixtures/traits.php b/tests/PhpGenerator/fixtures/traits.php index c9d009d2..f046ef90 100644 --- a/tests/PhpGenerator/fixtures/traits.php +++ b/tests/PhpGenerator/fixtures/traits.php @@ -46,6 +46,7 @@ public function f1() class Class1 extends ParentClass { + /** @use Foo */ use Trait2; } From a2e6a0c32f800fc38f7247565ee57d6376685484 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 23 Sep 2021 19:24:44 +0200 Subject: [PATCH 053/266] refactoring & fixes --- src/PhpGenerator/ClassType.php | 39 +++++---- src/PhpGenerator/Dumper.php | 5 +- src/PhpGenerator/EnumCase.php | 2 +- src/PhpGenerator/Extractor.php | 85 +++++++++----------- src/PhpGenerator/Helpers.php | 4 +- src/PhpGenerator/PhpNamespace.php | 2 +- src/PhpGenerator/Printer.php | 61 +++++++------- src/PhpGenerator/Traits/FunctionLike.php | 4 +- tests/PhpGenerator/expected/ClassType.expect | 2 +- 9 files changed, 97 insertions(+), 107 deletions(-) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 7f644b18..8503991f 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -101,7 +101,8 @@ public static function enum(string $name): self */ public static function from($class, bool $withBodies = false, bool $materializeTraits = true): self { - return (new Factory)->fromClassReflection(new \ReflectionClass($class), $withBodies, $materializeTraits); + return (new Factory) + ->fromClassReflection(new \ReflectionClass($class), $withBodies, $materializeTraits); } @@ -110,13 +111,15 @@ public static function from($class, bool $withBodies = false, bool $materializeT */ public static function withBodiesFrom($class): self { - return (new Factory)->fromClassReflection(new \ReflectionClass($class), true); + return (new Factory) + ->fromClassReflection(new \ReflectionClass($class), true); } public static function fromCode(string $code): self { - return (new Factory)->fromClassCode($code); + return (new Factory) + ->fromClassCode($code); } @@ -321,10 +324,7 @@ public function addImplement(string $name): self /** @return static */ public function removeImplement(string $name): self { - $key = array_search($name, $this->implements, true); - if ($key !== false) { - unset($this->implements[$key]); - } + $this->implements = array_diff($this->implements, [$name]); return $this; } @@ -424,10 +424,10 @@ public function addMember($member): self public function setConstants(array $consts): self { $this->consts = []; - foreach ($consts as $k => $v) { - $const = $v instanceof Constant - ? $v - : (new Constant($k))->setValue($v); + foreach ($consts as $k => $const) { + if (!$const instanceof Constant) { + $const = (new Constant($k))->setValue($const)->setPublic(); + } $this->consts[$const->getName()] = $const; } return $this; @@ -443,7 +443,9 @@ public function getConstants(): array public function addConstant(string $name, $value): Constant { - return $this->consts[$name] = (new Constant($name))->setValue($value)->setPublic(); + return $this->consts[$name] = (new Constant($name)) + ->setValue($value) + ->setPublic(); } @@ -457,7 +459,7 @@ public function removeConstant(string $name): self /** * Sets cases to enum - * @param EnumCase[] $consts + * @param EnumCase[] $cases * @return static */ public function setCases(array $cases): self @@ -481,7 +483,8 @@ public function getCases(): array /** Adds case to enum */ public function addCase(string $name, $value = null): EnumCase { - return $this->cases[$name] = (new EnumCase($name))->setValue($value); + return $this->cases[$name] = (new EnumCase($name)) + ->setValue($value); } @@ -499,11 +502,9 @@ public function removeCase(string $name): self */ public function setProperties(array $props): self { + (function (Property ...$props) {})(...$props); $this->properties = []; foreach ($props as $v) { - if (!$v instanceof Property) { - throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Property[].'); - } $this->properties[$v->getName()] = $v; } return $this; @@ -560,11 +561,9 @@ public function hasProperty(string $name): bool */ public function setMethods(array $methods): self { + (function (Method ...$methods) {})(...$methods); $this->methods = []; foreach ($methods as $v) { - if (!$v instanceof Method) { - throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Method[].'); - } $this->methods[$v->getName()] = $v; } return $this; diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 319ca20e..9f2e0af8 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -121,7 +121,7 @@ private function dumpArray(array &$var, array $parents, int $level, int $column) } - private function dumpObject(&$var, array $parents, int $level): string + private function dumpObject($var, array $parents, int $level): string { if ($var instanceof \Serializable) { return 'unserialize(' . $this->dumpString(serialize($var)) . ')'; @@ -244,10 +244,9 @@ private function dumpArguments(array &$var, int $column, bool $named): string /** - * @return object * @internal */ - public static function createObject(string $class, array $props) + public static function createObject(string $class, array $props): object { return unserialize('O' . substr(serialize($class), 1, -1) . substr(serialize($props), 1)); } diff --git a/src/PhpGenerator/EnumCase.php b/src/PhpGenerator/EnumCase.php index c3c497a1..3f25467a 100644 --- a/src/PhpGenerator/EnumCase.php +++ b/src/PhpGenerator/EnumCase.php @@ -22,7 +22,7 @@ final class EnumCase use Traits\CommentAware; use Traits\AttributeAware; - /** @var mixed */ + /** @var string|int|null */ private $value; diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index ff4a02a3..69c1dc24 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -76,6 +76,7 @@ public function extractMethodBodies(string $className): array public function extractFunctionBody(string $name): ?string { + /** @var Node\Stmt\Function_ $functionNode */ $functionNode = (new NodeFinder)->findFirst($this->statements, function (Node $node) use ($name) { return $node instanceof Node\Stmt\Function_ && $node->namespacedName->toString() === $name; }); @@ -87,9 +88,8 @@ public function extractFunctionBody(string $name): ?string /** @param Node[] $statements */ private function getReformattedBody(array $statements, int $level): string { - $replacements = $this->prepareReplacements($statements); $body = $this->getNodeContents(...$statements); - $body = $this->performReplacements($body, $replacements); + $body = $this->performReplacements($body, $this->prepareReplacements($statements)); return Helpers::unindent($body, $level); } @@ -98,55 +98,44 @@ private function prepareReplacements(array $statements): array { $start = $statements[0]->getStartFilePos(); $replacements = []; - $nodeFinder = new NodeFinder; - - // name-nodes => resolved fully-qualified name - foreach ($nodeFinder->findInstanceOf($statements, Node\Name\FullyQualified::class) as $node) { - if ($node->hasAttribute('originalName') - && $node->getAttribute('originalName') instanceof Node\Name - ) { - $replacements[] = [ - $node->getStartFilePos() - $start, - $node->getEndFilePos() - $start, - $node->toCodeString(), - ]; - } - } + (new NodeFinder)->find($statements, function (Node $node) use (&$replacements, $start) { + if ($node instanceof Node\Name\FullyQualified) { + if ($node->getAttribute('originalName') instanceof Node\Name) { + $replacements[] = [ + $node->getStartFilePos() - $start, + $node->getEndFilePos() - $start, + $node->toCodeString(), + ]; + } - // multi-line strings => singleline - foreach (array_merge( - $nodeFinder->findInstanceOf($statements, Node\Scalar\String_::class), - $nodeFinder->findInstanceOf($statements, Node\Scalar\EncapsedStringPart::class) - ) as $node) { - /** @var Node\Scalar\String_|Node\Scalar\EncapsedStringPart $node */ - $token = $this->getNodeContents($node); - if (strpos($token, "\n") !== false) { - $quote = $node instanceof Node\Scalar\String_ ? '"' : ''; - $replacements[] = [ - $node->getStartFilePos() - $start, - $node->getEndFilePos() - $start, - $quote . addcslashes($node->value, "\x00..\x1F") . $quote, - ]; - } - } + } elseif ($node instanceof Node\Scalar\String_ || $node instanceof Node\Scalar\EncapsedStringPart) { + // multi-line strings => singleline + $token = $this->getNodeContents($node); + if (strpos($token, "\n") !== false) { + $quote = $node instanceof Node\Scalar\String_ ? '"' : ''; + $replacements[] = [ + $node->getStartFilePos() - $start, + $node->getEndFilePos() - $start, + $quote . addcslashes($node->value, "\x00..\x1F") . $quote, + ]; + } - // HEREDOC => "string" - foreach ($nodeFinder->findInstanceOf($statements, Node\Scalar\Encapsed::class) as $node) { - /** @var Node\Scalar\Encapsed $node */ - if ($node->getAttribute('kind') === Node\Scalar\String_::KIND_HEREDOC) { - $replacements[] = [ - $node->getStartFilePos() - $start, - $node->parts[0]->getStartFilePos() - $start - 1, - '"', - ]; - $replacements[] = [ - end($node->parts)->getEndFilePos() - $start + 1, - $node->getEndFilePos() - $start, - '"', - ]; + } elseif ($node instanceof Node\Scalar\Encapsed) { + // HEREDOC => "string" + if ($node->getAttribute('kind') === Node\Scalar\String_::KIND_HEREDOC) { + $replacements[] = [ + $node->getStartFilePos() - $start, + $node->parts[0]->getStartFilePos() - $start - 1, + '"', + ]; + $replacements[] = [ + end($node->parts)->getEndFilePos() - $start + 1, + $node->getEndFilePos() - $start, + '"', + ]; + } } - } - + }); return $replacements; } diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 53a050dd..21d8f3b6 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -92,7 +92,7 @@ public static function unformatDocComment(string $comment): string public static function unindent(string $s, int $level = 1): string { return $level - ? preg_replace('#^(\t|\ \ \ \ ){1,' . $level . '}#m', '', $s) + ? preg_replace('#^(\t| {4}){1,' . $level . '}#m', '', $s) : $s; } @@ -131,7 +131,7 @@ public static function tabsToSpaces(string $s, int $count = 4): string /** @internal */ - public static function createObject(string $class, array $props) + public static function createObject(string $class, array $props): object { return Dumper::createObject($class, $props); } diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 83c4e115..2ffabfd3 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -114,7 +114,7 @@ public function addUse(string $name, string $alias = null, string &$aliasOut = n } elseif (isset($this->uses[$alias]) && $this->uses[$alias] !== $name) { throw new InvalidStateException( - "Alias '$alias' used already for '{$this->uses[$alias]}', cannot use for '{$name}'." + "Alias '$alias' used already for '{$this->uses[$alias]}', cannot use for '$name'." ); } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 19d93b9c..fea23865 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -116,11 +116,12 @@ public function printMethod(Method $method, PhpNamespace $namespace = null): str . ($method->getReturnReference() ? '&' : '') . $method->getName(); $returnType = $this->printReturnType($method); + $params = $this->printParameters($method, strlen($line) + strlen($returnType) + strlen($this->indentation) + 2); return Helpers::formatDocComment($method->getComment() . "\n") . self::printAttributes($method->getAttributes()) . $line - . ($params = $this->printParameters($method, strlen($line) + strlen($returnType) + strlen($this->indentation) + 2)) // 2 = parentheses + . $params . $returnType . ($method->isAbstract() || $method->getBody() === null ? ";\n" @@ -144,7 +145,9 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st $resolutions = $trait->getResolutions(); $traits[] = Helpers::formatDocComment((string) $trait->getComment()) . 'use ' . $resolver($trait->getName()) - . ($resolutions ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n" : ";\n"); + . ($resolutions + ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n" + : ";\n"); } $cases = []; @@ -185,7 +188,9 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st $properties[] = Helpers::formatDocComment((string) $property->getComment()) . self::printAttributes($property->getAttributes()) . $def - . ($property->getValue() === null && !$property->isInitialized() ? '' : ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = ' + . ($property->getValue() === null && !$property->isInitialized() + ? '' + : ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = ' . ";\n"; } @@ -264,28 +269,6 @@ 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 - { - $s = str_replace("\t", $this->indentation, $s); - return Strings::indent($s, 1, $this->indentation); - } - - - protected function dump($var, int $column = 0): string - { - $this->dumper->indentation = $this->indentation; - return $this->dumper->dump($var, $column); - } - - protected function printUses(PhpNamespace $namespace): string { $name = $namespace->getName(); @@ -304,7 +287,7 @@ protected function printUses(PhpNamespace $namespace): string /** * @param Closure|GlobalFunction|Method $function */ - public function printParameters($function, int $column = 0): string + protected function printParameters($function, int $column = 0): string { $params = []; $list = $function->getParameters(); @@ -339,7 +322,7 @@ public function printParameters($function, int $column = 0): string } - public function printType(?string $type, bool $nullable): string + protected function printType(?string $type, bool $nullable): string { if ($type === null) { return ''; @@ -384,7 +367,29 @@ private function printAttributes(array $attrs, bool $inline = false): string } - private function joinProperties(array $props) + /** @return static */ + public function setTypeResolving(bool $state = true): self + { + $this->resolveTypes = $state; + return $this; + } + + + protected function indent(string $s): string + { + $s = str_replace("\t", $this->indentation, $s); + return Strings::indent($s, 1, $this->indentation); + } + + + protected function dump($var, int $column = 0): string + { + $this->dumper->indentation = $this->indentation; + return $this->dumper->dump($var, $column); + } + + + private function joinProperties(array $props): string { return $this->linesBetweenProperties ? implode(str_repeat("\n", $this->linesBetweenProperties), $props) diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index 933ea91e..2c6a49b6 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -68,11 +68,9 @@ public function addBody(string $code, array $args = null): self */ public function setParameters(array $val): self { + (function (Parameter ...$val) {})(...$val); $this->parameters = []; foreach ($val as $v) { - if (!$v instanceof Parameter) { - throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Parameter[].'); - } $this->parameters[$v->getName()] = $v; } return $this; diff --git a/tests/PhpGenerator/expected/ClassType.expect b/tests/PhpGenerator/expected/ClassType.expect index a6373aa9..1be91229 100644 --- a/tests/PhpGenerator/expected/ClassType.expect +++ b/tests/PhpGenerator/expected/ClassType.expect @@ -16,7 +16,7 @@ abstract class Example extends ParentClass implements IExample, IOne b as private bar; } - const ROLE = 'admin'; + public const ROLE = 'admin'; final public const ACTIVE = false; /** Commented */ From fbd1e669489a807ce112fe50eef459d62d28c8fe Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 21 Sep 2021 14:49:06 +0200 Subject: [PATCH 054/266] tests: improved --- .../Extractor.extractAll.resolving.phpt | 21 ++++ .../expected/ClassType.from.bodies.expect | 20 ++++ .../expected/Factory.fromCode.bodies.expect | 20 ++++ .../Factory.fromCode.bodies.resolving.expect | 102 ++++++++++++++++++ tests/PhpGenerator/fixtures/bodies.php | 20 ++++ 5 files changed, 183 insertions(+) create mode 100644 tests/PhpGenerator/Extractor.extractAll.resolving.phpt create mode 100644 tests/PhpGenerator/expected/Factory.fromCode.bodies.resolving.expect diff --git a/tests/PhpGenerator/Extractor.extractAll.resolving.phpt b/tests/PhpGenerator/Extractor.extractAll.resolving.phpt new file mode 100644 index 00000000..080cb895 --- /dev/null +++ b/tests/PhpGenerator/Extractor.extractAll.resolving.phpt @@ -0,0 +1,21 @@ +extractAll(); +$classes = $file->getClasses(); + +$namespace = new PhpNamespace('Nette'); +$namespace->addUse('Abc\a\FOO'); // must not be confused with constant +$namespace->addUse('Abc\a\func'); // must not be confused with func +$namespace->add(reset($classes)); + +$printer = new Printer; +sameFile(__DIR__ . '/expected/Factory.fromCode.bodies.resolving.expect', $printer->printNamespace($namespace)); diff --git a/tests/PhpGenerator/expected/ClassType.from.bodies.expect b/tests/PhpGenerator/expected/ClassType.from.bodies.expect index 729902bc..0b5dc39d 100644 --- a/tests/PhpGenerator/expected/ClassType.from.bodies.expect +++ b/tests/PhpGenerator/expected/ClassType.from.bodies.expect @@ -37,6 +37,26 @@ abstract class Class7 } + public function resolving($a = Abc\a\FOO, self $b = null, $c = self::FOO) + { + echo FOO; + echo \FOO; + echo \Abc\a\FOO; + echo \Nette\FOO; + + // functions + func(); + \func(); + \Abc\a\func(); + \Nette\func(); + + // classes + $x = new \Abc\MyClass; + $y = new \stdClass; + $z = \Nette\Utils\ArrayHash::class; + } + + public function complex() { echo 1; diff --git a/tests/PhpGenerator/expected/Factory.fromCode.bodies.expect b/tests/PhpGenerator/expected/Factory.fromCode.bodies.expect index 07609c12..af201c80 100644 --- a/tests/PhpGenerator/expected/Factory.fromCode.bodies.expect +++ b/tests/PhpGenerator/expected/Factory.fromCode.bodies.expect @@ -45,6 +45,26 @@ abstract class Class7 } + public function resolving($a = \Abc\a\FOO, self $b = null, $c = self::FOO) + { + echo FOO; + echo \FOO; + echo \Abc\a\FOO; + echo \Nette\FOO; + + // functions + func(); + \func(); + \Abc\a\func(); + \Nette\func(); + + // classes + $x = new \Abc\MyClass; + $y = new \stdClass; + $z = \Nette\Utils\ArrayHash::class; + } + + public function complex() { echo 1; diff --git a/tests/PhpGenerator/expected/Factory.fromCode.bodies.resolving.expect b/tests/PhpGenerator/expected/Factory.fromCode.bodies.resolving.expect new file mode 100644 index 00000000..4aaf061d --- /dev/null +++ b/tests/PhpGenerator/expected/Factory.fromCode.bodies.resolving.expect @@ -0,0 +1,102 @@ +namespace Nette; + +use Abc\a\FOO; +use Abc\a\func; + +abstract class Class7 +{ + abstract public function abstractFun(); + + + public function emptyFun() + { + } + + + public function emptyFun2() + { + } + + + public function simple() + { + return 1; + } + + + public function simple2() + { + return 1; + } + + + public function long() + { + if ($member instanceof \Abc\Method) { + $s = [1, 2, 3]; + } + /* + $this->methods[$member->getName()] = $member; + */ + throw new \Nette\InvalidArgumentException('Argument must be Method|Property|Constant.'); + } + + + public function resolving($a = \Abc\a\FOO, self $b = null, $c = self::FOO) + { + echo FOO; + echo \FOO; + echo \Abc\a\FOO; + echo \Nette\FOO; + + // functions + func(); + \func(); + \Abc\a\func(); + \Nette\func(); + + // classes + $x = new \Abc\MyClass; + $y = new \stdClass; + $z = \Nette\Utils\ArrayHash::class; + } + + + public function complex() + { + echo 1; + // single line comment + + // spaces - indent + // spaces - indent + + /* multi + line + comment */ + if ( + $a + && $b + $c) + {} + + /** multi + line + comment */ + // Alias Method will not be resolved in comment + if ($member instanceof \Abc\Method) { + $s1 = "\na\n\tb\n\t\tc\n"; + $s2 = "\na\n\t{$b}\n\t\t$c\n"; + + $s3 = "a\n\t{$b}\n\t\t$c" + ; + $s3 = "a\n\tb\n\t\tc" + ; + // inline HTML is not supported + ?> + a + b + c + Date: Thu, 23 Sep 2021 19:25:20 +0200 Subject: [PATCH 055/266] Resolving of names in body & literals [Closes #85] --- src/PhpGenerator/Extractor.php | 23 ++-- src/PhpGenerator/Helpers.php | 21 ++++ src/PhpGenerator/PhpNamespace.php | 5 + src/PhpGenerator/Printer.php | 17 ++- .../Extractor.extractAll.resolving.phpt | 3 + tests/PhpGenerator/PhpNamespace.phpt | 2 + .../expected/ClassType.from.bodies.expect | 10 +- .../expected/Factory.fromCode.bodies.expect | 22 ++-- .../Factory.fromCode.bodies.resolving.expect | 6 +- ...Factory.fromCode.bodies.unresolving.expect | 102 ++++++++++++++++++ .../expected/Factory.fromCode.expect | 2 +- 11 files changed, 179 insertions(+), 34 deletions(-) create mode 100644 tests/PhpGenerator/expected/Factory.fromCode.bodies.unresolving.expect diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 69c1dc24..bf3e2ed8 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -50,6 +50,7 @@ private function parseCode(string $code): void $stmts = $parser->parse($this->code); $traverser = new PhpParser\NodeTraverser; + $traverser->addVisitor(new PhpParser\NodeVisitor\ParentConnectingVisitor); $traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver(null, ['preserveOriginalNames' => true])); $this->statements = $traverser->traverse($stmts); } @@ -67,7 +68,7 @@ public function extractMethodBodies(string $className): array foreach ($nodeFinder->findInstanceOf($classNode, Node\Stmt\ClassMethod::class) as $methodNode) { /** @var Node\Stmt\ClassMethod $methodNode */ if ($methodNode->stmts) { - $res[$methodNode->name->toString()] = $this->getReformattedBody($methodNode->stmts, 2); + $res[$methodNode->name->toString()] = $this->getReformattedContents($methodNode->stmts, 2); } } return $res; @@ -81,12 +82,12 @@ public function extractFunctionBody(string $name): ?string return $node instanceof Node\Stmt\Function_ && $node->namespacedName->toString() === $name; }); - return $this->getReformattedBody($functionNode->stmts, 1); + return $this->getReformattedContents($functionNode->stmts, 1); } /** @param Node[] $statements */ - private function getReformattedBody(array $statements, int $level): string + private function getReformattedContents(array $statements, int $level): string { $body = $this->getNodeContents(...$statements); $body = $this->performReplacements($body, $this->prepareReplacements($statements)); @@ -101,10 +102,13 @@ private function prepareReplacements(array $statements): array (new NodeFinder)->find($statements, function (Node $node) use (&$replacements, $start) { if ($node instanceof Node\Name\FullyQualified) { if ($node->getAttribute('originalName') instanceof Node\Name) { + $type = $node->getAttribute('parent') instanceof Node\Expr\ConstFetch + ? PhpNamespace::NAME_CONSTANT + : ($node->getAttribute('parent') instanceof Node\Expr\FuncCall ? PhpNamespace::NAME_FUNCTION : PhpNamespace::NAME_NORMAL); $replacements[] = [ $node->getStartFilePos() - $start, $node->getEndFilePos() - $start, - $node->toCodeString(), + Helpers::tagName($node->toCodeString(), $type), ]; } @@ -302,7 +306,7 @@ private function addPropertyToClass(ClassType $class, Node\Stmt\Property $node): } $prop->setType($node->type ? $this->toPhp($node->type) : null); if ($item->default) { - $prop->setValue(new Literal($this->toPhp($item->default))); + $prop->setValue(new Literal($this->getReformattedContents([$item->default], 1))); } $prop->setReadOnly(method_exists($node, 'isReadonly') && $node->isReadonly()); $this->addCommentAndAttributes($prop, $node); @@ -328,7 +332,8 @@ private function addMethodToClass(ClassType $class, Node\Stmt\ClassMethod $node) private function addConstantToClass(ClassType $class, Node\Stmt\ClassConst $node): void { foreach ($node->consts as $item) { - $const = $class->addConstant($item->name->toString(), new Literal($this->toPhp($item->value))); + $value = $this->getReformattedContents([$item->value], 1); + $const = $class->addConstant($item->name->toString(), new Literal($value)); if ($node->isPrivate()) { $const->setPrivate(); } elseif ($node->isProtected()) { @@ -359,7 +364,7 @@ private function addCommentAndAttributes($element, Node $node): void foreach ($group->attrs as $attribute) { $args = []; foreach ($attribute->args as $arg) { - $value = new Literal($this->toPhp($arg)); + $value = new Literal($this->getReformattedContents([$arg], 0)); if ($arg->name) { $args[$arg->name->toString()] = $value; } else { @@ -385,13 +390,13 @@ private function setupFunction($function, Node\FunctionLike $node): void $param->setReference($item->byRef); $function->setVariadic($item->variadic); if ($item->default) { - $param->setDefaultValue(new Literal($this->toPhp($item->default))); + $param->setDefaultValue(new Literal($this->getReformattedContents([$item->default], 2))); } $this->addCommentAndAttributes($param, $item); } $this->addCommentAndAttributes($function, $node); if ($node->stmts) { - $function->setBody($this->getReformattedBody($node->stmts, 2)); + $function->setBody($this->getReformattedContents($node->stmts, 2)); } } diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 21d8f3b6..5c10064d 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -83,6 +83,27 @@ public static function formatDocComment(string $content): string } + public static function tagName(string $name, string $type = PhpNamespace::NAME_NORMAL): string + { + return "/*($type*/$name"; + } + + + public static function simplifyTaggedNames(string $code, ?PhpNamespace $namespace): string + { + return preg_replace_callback('~/\*\(([ncf])\*/([\w\x7f-\xff\\\\]++)~', function ($m) use ($namespace) { + [, $type, $name] = $m; + if (!$namespace) { + return $name; + } elseif ($type === PhpNamespace::NAME_NORMAL) { + return $namespace->simplifyType($name); + } else { + return $namespace->simplifyType(self::extractNamespace($name) . '\\') . self::extractShortName($name); + } + }, $code); + } + + public static function unformatDocComment(string $comment): string { return preg_replace('#^\s*\* ?#m', '', trim(trim(trim($comment), '/*'))); diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 2ffabfd3..32275036 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -26,6 +26,11 @@ final class PhpNamespace { use Nette\SmartObject; + public const + NAME_NORMAL = 'n', + NAME_FUNCTION = 'f', + NAME_CONSTANT = 'c'; + /** @var string */ private $name; diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index fea23865..5ea6bc01 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -55,13 +55,14 @@ public function printFunction(GlobalFunction $function, PhpNamespace $namespace . ($function->getReturnReference() ? '&' : '') . $function->getName(); $returnType = $this->printReturnType($function); + $body = Helpers::simplifyTaggedNames($function->getBody(), $this->namespace); return Helpers::formatDocComment($function->getComment() . "\n") . self::printAttributes($function->getAttributes()) . $line . $this->printParameters($function, strlen($line) + strlen($returnType) + 2) // 2 = parentheses . $returnType - . "\n{\n" . $this->indent(ltrim(rtrim($function->getBody()) . "\n")) . "}\n"; + . "\n{\n" . $this->indent(ltrim(rtrim($body) . "\n")) . "}\n"; } @@ -75,6 +76,7 @@ public function printClosure(Closure $closure, PhpNamespace $namespace = null): $useStr = strlen($tmp = implode(', ', $uses)) > $this->dumper->wrapLength && count($uses) > 1 ? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . "\n" : $tmp; + $body = Helpers::simplifyTaggedNames($closure->getBody(), $this->namespace); return self::printAttributes($closure->getAttributes(), true) . 'function ' @@ -82,7 +84,7 @@ public function printClosure(Closure $closure, PhpNamespace $namespace = null): . $this->printParameters($closure) . ($uses ? " use ($useStr)" : '') . $this->printReturnType($closure) - . " {\n" . $this->indent(ltrim(rtrim($closure->getBody()) . "\n")) . '}'; + . " {\n" . $this->indent(ltrim(rtrim($body) . "\n")) . '}'; } @@ -94,13 +96,14 @@ public function printArrowFunction(Closure $closure, PhpNamespace $namespace = n throw new Nette\InvalidArgumentException('Arrow function cannot bind variables by-reference.'); } } + $body = Helpers::simplifyTaggedNames($closure->getBody(), $this->namespace); return self::printAttributes($closure->getAttributes()) . 'fn' . ($closure->getReturnReference() ? '&' : '') . $this->printParameters($closure) . $this->printReturnType($closure) - . ' => ' . trim($closure->getBody()) . ';'; + . ' => ' . trim($body) . ';'; } @@ -117,6 +120,7 @@ public function printMethod(Method $method, PhpNamespace $namespace = null): str . $method->getName(); $returnType = $this->printReturnType($method); $params = $this->printParameters($method, strlen($line) + strlen($returnType) + strlen($this->indentation) + 2); + $body = Helpers::simplifyTaggedNames((string) $method->getBody(), $this->namespace); return Helpers::formatDocComment($method->getComment() . "\n") . self::printAttributes($method->getAttributes()) @@ -127,7 +131,7 @@ public function printMethod(Method $method, PhpNamespace $namespace = null): str ? ";\n" : (strpos($params, "\n") === false ? "\n" : ' ') . "{\n" - . $this->indent(ltrim(rtrim($method->getBody()) . "\n")) + . $this->indent(ltrim(rtrim($body) . "\n")) . "}\n"); } @@ -359,6 +363,7 @@ private function printAttributes(array $attrs, bool $inline = false): string $items = []; foreach ($attrs as $attr) { $args = $this->dumper->format('...?:', $attr->getArguments()); + $args = Helpers::simplifyTaggedNames($args, $this->namespace); $items[] = $this->printType($attr->getName(), false) . ($args ? "($args)" : ''); } return $inline @@ -385,7 +390,9 @@ protected function indent(string $s): string protected function dump($var, int $column = 0): string { $this->dumper->indentation = $this->indentation; - return $this->dumper->dump($var, $column); + $s = $this->dumper->dump($var, $column); + $s = Helpers::simplifyTaggedNames($s, $this->namespace); + return $s; } diff --git a/tests/PhpGenerator/Extractor.extractAll.resolving.phpt b/tests/PhpGenerator/Extractor.extractAll.resolving.phpt index 080cb895..c927f541 100644 --- a/tests/PhpGenerator/Extractor.extractAll.resolving.phpt +++ b/tests/PhpGenerator/Extractor.extractAll.resolving.phpt @@ -19,3 +19,6 @@ $namespace->add(reset($classes)); $printer = new Printer; sameFile(__DIR__ . '/expected/Factory.fromCode.bodies.resolving.expect', $printer->printNamespace($namespace)); + +$printer->setTypeResolving(false); +sameFile(__DIR__ . '/expected/Factory.fromCode.bodies.unresolving.expect', $printer->printNamespace($namespace)); diff --git a/tests/PhpGenerator/PhpNamespace.phpt b/tests/PhpGenerator/PhpNamespace.phpt index a86403c6..4bf78e49 100644 --- a/tests/PhpGenerator/PhpNamespace.phpt +++ b/tests/PhpGenerator/PhpNamespace.phpt @@ -34,7 +34,9 @@ $namespace = new PhpNamespace('Foo'); Assert::same('Foo', $namespace->getName()); Assert::same('\A', $namespace->simplifyName('\A')); Assert::same('\A', $namespace->simplifyName('A')); +Assert::same('\A\\', $namespace->simplifyName('A\\')); Assert::same('A', $namespace->simplifyName('foo\A')); +Assert::same('A\\', $namespace->simplifyName('foo\A\\')); Assert::same('A', $namespace->simplifyType('foo\A')); Assert::same('null|A', $namespace->simplifyType('null|foo\A')); diff --git a/tests/PhpGenerator/expected/ClassType.from.bodies.expect b/tests/PhpGenerator/expected/ClassType.from.bodies.expect index 0b5dc39d..1ce8fe8f 100644 --- a/tests/PhpGenerator/expected/ClassType.from.bodies.expect +++ b/tests/PhpGenerator/expected/ClassType.from.bodies.expect @@ -27,7 +27,7 @@ abstract class Class7 public function long() { - if ($member instanceof \Abc\Method) { + if ($member instanceof Method) { $s = [1, 2, 3]; } /* @@ -41,17 +41,17 @@ abstract class Class7 { echo FOO; echo \FOO; - echo \Abc\a\FOO; + echo a\FOO; echo \Nette\FOO; // functions func(); \func(); - \Abc\a\func(); + a\func(); \Nette\func(); // classes - $x = new \Abc\MyClass; + $x = new MyClass; $y = new \stdClass; $z = \Nette\Utils\ArrayHash::class; } @@ -77,7 +77,7 @@ abstract class Class7 line comment */ // Alias Method will not be resolved in comment - if ($member instanceof \Abc\Method) { + if ($member instanceof Method) { $s1 = "\na\n\tb\n\t\tc\n"; $s2 = "\na\n\t{$b}\n\t\t$c\n"; diff --git a/tests/PhpGenerator/expected/Factory.fromCode.bodies.expect b/tests/PhpGenerator/expected/Factory.fromCode.bodies.expect index af201c80..4ec14cae 100644 --- a/tests/PhpGenerator/expected/Factory.fromCode.bodies.expect +++ b/tests/PhpGenerator/expected/Factory.fromCode.bodies.expect @@ -35,33 +35,33 @@ abstract class Class7 public function long() { - if ($member instanceof \Abc\Method) { + if ($member instanceof Method) { $s = [1, 2, 3]; } /* $this->methods[$member->getName()] = $member; */ - throw new \Nette\InvalidArgumentException('Argument must be Method|Property|Constant.'); + throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant.'); } - public function resolving($a = \Abc\a\FOO, self $b = null, $c = self::FOO) + public function resolving($a = a\FOO, self $b = null, $c = self::FOO) { echo FOO; echo \FOO; - echo \Abc\a\FOO; - echo \Nette\FOO; + echo a\FOO; + echo Nette\FOO; // functions func(); \func(); - \Abc\a\func(); - \Nette\func(); + a\func(); + Nette\func(); // classes - $x = new \Abc\MyClass; + $x = new MyClass; $y = new \stdClass; - $z = \Nette\Utils\ArrayHash::class; + $z = Nette\Utils\ArrayHash::class; } @@ -85,7 +85,7 @@ abstract class Class7 line comment */ // Alias Method will not be resolved in comment - if ($member instanceof \Abc\Method) { + if ($member instanceof Method) { $s1 = "\na\n\tb\n\t\tc\n"; $s2 = "\na\n\t{$b}\n\t\t$c\n"; @@ -100,6 +100,6 @@ abstract class Class7 c methods[$member->getName()] = $member; */ - throw new \Nette\InvalidArgumentException('Argument must be Method|Property|Constant.'); + throw new InvalidArgumentException('Argument must be Method|Property|Constant.'); } @@ -58,7 +58,7 @@ abstract class Class7 // classes $x = new \Abc\MyClass; $y = new \stdClass; - $z = \Nette\Utils\ArrayHash::class; + $z = Utils\ArrayHash::class; } @@ -97,6 +97,6 @@ abstract class Class7 c methods[$member->getName()] = $member; + */ + throw new \Nette\InvalidArgumentException('Argument must be Method|Property|Constant.'); + } + + + public function resolving($a = \Abc\a\FOO, self $b = null, $c = self::FOO) + { + echo FOO; + echo \FOO; + echo \Abc\a\FOO; + echo \Nette\FOO; + + // functions + func(); + \func(); + \Abc\a\func(); + \Nette\func(); + + // classes + $x = new \Abc\MyClass; + $y = new \stdClass; + $z = \Nette\Utils\ArrayHash::class; + } + + + public function complex() + { + echo 1; + // single line comment + + // spaces - indent + // spaces - indent + + /* multi + line + comment */ + if ( + $a + && $b + $c) + {} + + /** multi + line + comment */ + // Alias Method will not be resolved in comment + if ($member instanceof \Abc\Method) { + $s1 = "\na\n\tb\n\t\tc\n"; + $s2 = "\na\n\t{$b}\n\t\t$c\n"; + + $s3 = "a\n\t{$b}\n\t\t$c" + ; + $s3 = "a\n\tb\n\t\tc" + ; + // inline HTML is not supported + ?> + a + b + c + Date: Wed, 22 Sep 2021 00:31:08 +0200 Subject: [PATCH 056/266] Factory: constant values are tagged for resolving --- src/PhpGenerator/Factory.php | 12 +++++++++--- src/PhpGenerator/Helpers.php | 4 +++- tests/PhpGenerator/expected/ClassType.from.expect | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 6f833457..cfa57633 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -219,9 +219,15 @@ public function fromParameterReflection(\ReflectionParameter $from): Parameter $param->setType((string) $from->getType()); } if ($from->isDefaultValueAvailable()) { - $param->setDefaultValue($from->isDefaultValueConstant() - ? new Literal($from->getDefaultValueConstantName()) - : $from->getDefaultValue()); + if ($from->isDefaultValueConstant()) { + $parts = explode('::', $from->getDefaultValueConstantName()); + if (count($parts) > 1) { + $parts[0] = Helpers::tagName($parts[0]); + } + $param->setDefaultValue(new Literal(implode('::', $parts))); + } else { + $param->setDefaultValue($from->getDefaultValue()); + } $param->setNullable($param->isNullable() && $param->getDefaultValue() !== null); } $param->setAttributes(self::getAttributes($from)); diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 5c10064d..2de0d1f1 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -85,7 +85,9 @@ public static function formatDocComment(string $content): string public static function tagName(string $name, string $type = PhpNamespace::NAME_NORMAL): string { - return "/*($type*/$name"; + return isset(self::KEYWORDS[strtolower($name)]) + ? $name + : "/*($type*/$name"; } diff --git a/tests/PhpGenerator/expected/ClassType.from.expect b/tests/PhpGenerator/expected/ClassType.from.expect index fa1d5120..23735a52 100644 --- a/tests/PhpGenerator/expected/ClassType.from.expect +++ b/tests/PhpGenerator/expected/ClassType.from.expect @@ -55,7 +55,7 @@ class Class2 extends Class1 implements Interface2 } - private function func4(array $a = [], Class2 $b = null, $c = Abc\Unknown::ABC) + private function func4(array $a = [], Class2 $b = null, $c = Unknown::ABC) { } From 4f4af9ab368d807754b0f4f2654062b87b3ae89d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 23 Sep 2021 18:25:18 +0200 Subject: [PATCH 057/266] getType(), getReturnType(): added option $asObject that returns Nette\Utils\Type (requires nette/utils 3.2.5) --- src/PhpGenerator/Parameter.php | 10 ++++++++-- src/PhpGenerator/Property.php | 10 ++++++++-- src/PhpGenerator/Traits/FunctionLike.php | 10 ++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/PhpGenerator/Parameter.php b/src/PhpGenerator/Parameter.php index 2d715275..706c8d86 100644 --- a/src/PhpGenerator/Parameter.php +++ b/src/PhpGenerator/Parameter.php @@ -10,6 +10,7 @@ namespace Nette\PhpGenerator; use Nette; +use Nette\Utils\Type; /** @@ -61,9 +62,14 @@ public function setType(?string $type): self } - public function getType(): ?string + /** + * @return Type|string|null + */ + public function getType(bool $asObject = false) { - return $this->type; + return $asObject && $this->type + ? Type::fromString($this->type) + : $this->type; } diff --git a/src/PhpGenerator/Property.php b/src/PhpGenerator/Property.php index f5ed5abf..04c95e50 100644 --- a/src/PhpGenerator/Property.php +++ b/src/PhpGenerator/Property.php @@ -10,6 +10,7 @@ namespace Nette\PhpGenerator; use Nette; +use Nette\Utils\Type; /** @@ -81,9 +82,14 @@ public function setType(?string $type): self } - public function getType(): ?string + /** + * @return Type|string|null + */ + public function getType(bool $asObject = false) { - return $this->type; + return $asObject && $this->type + ? Type::fromString($this->type) + : $this->type; } diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index 2c6a49b6..f446bd8b 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -12,6 +12,7 @@ use Nette; use Nette\PhpGenerator\Dumper; use Nette\PhpGenerator\Parameter; +use Nette\Utils\Type; /** @@ -130,9 +131,14 @@ public function setReturnType(?string $type): self } - public function getReturnType(): ?string + /** + * @return Type|string|null + */ + public function getReturnType(bool $asObject = false) { - return $this->returnType; + return $asObject && $this->returnType + ? Type::fromString($this->returnType) + : $this->returnType; } From bce6abcd4090ab5eec24b78f26c753c6525a425c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 23 Sep 2021 19:17:03 +0200 Subject: [PATCH 058/266] Printer: added property $wrapLength [Closes #55][Closes #56] --- src/PhpGenerator/Printer.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 5ea6bc01..ed03a98e 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -20,6 +20,9 @@ class Printer { use Nette\SmartObject; + /** @var int */ + public $wrapLength = 120; + /** @var string */ protected $indentation = "\t"; @@ -73,7 +76,7 @@ public function printClosure(Closure $closure, PhpNamespace $namespace = null): foreach ($closure->getUses() as $param) { $uses[] = ($param->isReference() ? '&' : '') . '$' . $param->getName(); } - $useStr = strlen($tmp = implode(', ', $uses)) > $this->dumper->wrapLength && count($uses) > 1 + $useStr = strlen($tmp = implode(', ', $uses)) > $this->wrapLength && count($uses) > 1 ? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . "\n" : $tmp; $body = Helpers::simplifyTaggedNames($closure->getBody(), $this->namespace); @@ -320,7 +323,7 @@ protected function printParameters($function, int $column = 0): string $line = implode(', ', $params); - return count($params) > 1 && ($special || strlen($line) + $column > $this->dumper->wrapLength) + return count($params) > 1 && ($special || strlen($line) + $column > $this->wrapLength) ? "(\n" . $this->indent(implode(",\n", $params)) . ($special ? ',' : '') . "\n)" : "($line)"; } @@ -390,6 +393,7 @@ protected function indent(string $s): string protected function dump($var, int $column = 0): string { $this->dumper->indentation = $this->indentation; + $this->dumper->wrapLength = $this->wrapLength; $s = $this->dumper->dump($var, $column); $s = Helpers::simplifyTaggedNames($s, $this->namespace); return $s; From c72e05bd9928097135210035024f7d36db9812d7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 1 Oct 2021 07:34:58 +0200 Subject: [PATCH 059/266] accept numeric array keys when validating types [Closes #91] --- src/PhpGenerator/ClassType.php | 6 +++--- src/PhpGenerator/Traits/FunctionLike.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 8503991f..4f8205b6 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -464,7 +464,7 @@ public function removeConstant(string $name): self */ public function setCases(array $cases): self { - (function (EnumCase ...$cases) {})(...$cases); + (function (EnumCase ...$cases) {})(...array_values($cases)); $this->cases = []; foreach ($cases as $case) { $this->cases[$case->getName()] = $case; @@ -502,7 +502,7 @@ public function removeCase(string $name): self */ public function setProperties(array $props): self { - (function (Property ...$props) {})(...$props); + (function (Property ...$props) {})(...array_values($props)); $this->properties = []; foreach ($props as $v) { $this->properties[$v->getName()] = $v; @@ -561,7 +561,7 @@ public function hasProperty(string $name): bool */ public function setMethods(array $methods): self { - (function (Method ...$methods) {})(...$methods); + (function (Method ...$methods) {})(...array_values($methods)); $this->methods = []; foreach ($methods as $v) { $this->methods[$v->getName()] = $v; diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index f446bd8b..151e73e2 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -69,7 +69,7 @@ public function addBody(string $code, array $args = null): self */ public function setParameters(array $val): self { - (function (Parameter ...$val) {})(...$val); + (function (Parameter ...$val) {})(...array_values($val)); $this->parameters = []; foreach ($val as $v) { $this->parameters[$v->getName()] = $v; From e6783b4910e0be1b043fd45fb9aa50656a8fca1b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 4 Oct 2021 15:53:16 +0200 Subject: [PATCH 060/266] Factory: supports 'new' in parameters For full support, reflection is missing in PHP. --- src/PhpGenerator/Factory.php | 16 +++++++++- tests/PhpGenerator/Extractor.extractAll.phpt | 4 +-- .../expected/ClassType.from.81.expect | 6 ++++ .../expected/Factory.fromCode.81.expect | 29 +++++++++++++++++++ tests/PhpGenerator/fixtures/classes.81.php | 14 ++++++++- 5 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 tests/PhpGenerator/expected/Factory.fromCode.81.expect diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index cfa57633..bd1a0c50 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -225,6 +225,8 @@ public function fromParameterReflection(\ReflectionParameter $from): Parameter $parts[0] = Helpers::tagName($parts[0]); } $param->setDefaultValue(new Literal(implode('::', $parts))); + } elseif (is_object($from->getDefaultValue())) { + $param->setDefaultValue($this->fromObject($from->getDefaultValue())); } else { $param->setDefaultValue($from->getDefaultValue()); } @@ -285,6 +287,12 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property } + public function fromObject(object $obj): Literal + { + return new Literal('new ' . get_class($obj) . '(/* unknown */)'); + } + + public function fromClassCode(string $code): ClassType { $classes = $this->fromCode($code)->getClasses(); @@ -308,7 +316,13 @@ private function getAttributes($from): array return []; } return array_map(function ($attr) { - return new Attribute($attr->getName(), $attr->getArguments()); + $args = $attr->getArguments(); + foreach ($args as &$arg) { + if (is_object($arg)) { + $arg = $this->fromObject($arg); + } + } + return new Attribute($attr->getName(), $args); }, $from->getAttributes()); } diff --git a/tests/PhpGenerator/Extractor.extractAll.phpt b/tests/PhpGenerator/Extractor.extractAll.phpt index 02737623..713b7332 100644 --- a/tests/PhpGenerator/Extractor.extractAll.phpt +++ b/tests/PhpGenerator/Extractor.extractAll.phpt @@ -18,8 +18,8 @@ sameFile(__DIR__ . '/expected/Factory.fromCode.74.expect', (string) $file); $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.80.php')))->extractAll(); sameFile(__DIR__ . '/expected/Factory.fromCode.80.expect', (string) $file); -//$file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.81.php')))->extractAll(); -//sameFile(__DIR__ . '/expected/Factory.fromCode.81.expect', (string) $file); +$file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.81.php')))->extractAll(); +sameFile(__DIR__ . '/expected/Factory.fromCode.81.expect', (string) $file); $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/enum.php')))->extractAll(); sameFile(__DIR__ . '/expected/Factory.fromCode.enum.expect', (string) $file); diff --git a/tests/PhpGenerator/expected/ClassType.from.81.expect b/tests/PhpGenerator/expected/ClassType.from.81.expect index 0c044c35..2200d27e 100644 --- a/tests/PhpGenerator/expected/ClassType.from.81.expect +++ b/tests/PhpGenerator/expected/ClassType.from.81.expect @@ -1,3 +1,4 @@ +#[Attr(new Abc\Attr(/* unknown */))] class Class11 { final public const FOO = 10; @@ -9,4 +10,9 @@ class Class11 public function foo(Foo&Bar $c): Foo&Bar { } + + + public function bar($c = new stdClass(/* unknown */)) + { + } } diff --git a/tests/PhpGenerator/expected/Factory.fromCode.81.expect b/tests/PhpGenerator/expected/Factory.fromCode.81.expect new file mode 100644 index 00000000..427d927b --- /dev/null +++ b/tests/PhpGenerator/expected/Factory.fromCode.81.expect @@ -0,0 +1,29 @@ + Date: Wed, 13 Oct 2021 03:07:04 +0200 Subject: [PATCH 061/266] composer: updated dependencies --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 53c9ad75..388d21cf 100644 --- a/composer.json +++ b/composer.json @@ -20,8 +20,8 @@ }, "require-dev": { "nette/tester": "^2.4", - "nikic/php-parser": "^4.11", - "tracy/tracy": "^2.3", + "nikic/php-parser": "^4.13", + "tracy/tracy": "^2.8", "phpstan/phpstan": "^0.12" }, "suggest": { From f457315b525ead2f3d690fae007c67b9792d4b6a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 2 Oct 2021 22:13:36 +0200 Subject: [PATCH 062/266] improved tests --- tests/PhpGenerator/PhpNamespace.aliases.phpt | 106 +++++++++++++++++++ tests/PhpGenerator/PhpNamespace.phpt | 72 ------------- tests/PhpGenerator/PhpNamespace.print.phpt | 35 ++++++ 3 files changed, 141 insertions(+), 72 deletions(-) create mode 100644 tests/PhpGenerator/PhpNamespace.aliases.phpt create mode 100644 tests/PhpGenerator/PhpNamespace.print.phpt diff --git a/tests/PhpGenerator/PhpNamespace.aliases.phpt b/tests/PhpGenerator/PhpNamespace.aliases.phpt new file mode 100644 index 00000000..a2426215 --- /dev/null +++ b/tests/PhpGenerator/PhpNamespace.aliases.phpt @@ -0,0 +1,106 @@ +getName()); +Assert::same('A', $namespace->simplifyName('A')); +Assert::same('foo\A', $namespace->simplifyName('foo\A')); + +$namespace->addUse('Bar\C'); + +Assert::same('Bar', $namespace->simplifyName('Bar')); +Assert::same('C', $namespace->simplifyName('bar\C')); +Assert::same('C\D', $namespace->simplifyName('Bar\C\D')); + +foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { + Assert::same($type, $namespace->simplifyName($type)); +} + + +// namespace +$namespace = new PhpNamespace('Foo'); + +Assert::same('Foo', $namespace->getName()); +Assert::same('\A', $namespace->simplifyName('\A')); +Assert::same('\A', $namespace->simplifyName('A')); +Assert::same('A', $namespace->simplifyName('foo\A')); + +Assert::same('A', $namespace->simplifyType('foo\A')); +Assert::same('null|A', $namespace->simplifyType('null|foo\A')); +Assert::same('?A', $namespace->simplifyType('?foo\A')); +Assert::same('A&\Countable', $namespace->simplifyType('foo\A&Countable')); +Assert::same('', $namespace->simplifyType('')); + +$namespace->addUse('Foo'); +Assert::same('B', $namespace->simplifyName('Foo\B')); + +$namespace->addUse('Bar\C'); +Assert::same(['C' => 'Bar\C', 'Foo' => 'Foo'], $namespace->getUses()); +Assert::same('C', $namespace->simplifyName('Foo\C')); + +Assert::same('\Bar', $namespace->simplifyName('Bar')); +Assert::same('C', $namespace->simplifyName('\bar\C')); +Assert::same('C', $namespace->simplifyName('bar\C')); +Assert::same('C\D', $namespace->simplifyName('Bar\C\D')); +Assert::same('A', $namespace->simplifyType('foo\A<\bar\C, Bar\C\D>')); +Assert::same('žluťoučký', $namespace->simplifyType('foo\žluťoučký')); + + +// duplicity +$namespace = new PhpNamespace('Foo'); +$namespace->addUse('Bar\C'); + +Assert::exception(function () use ($namespace) { + $namespace->addTrait('C'); +}, Nette\InvalidStateException::class, "Alias 'C' used already for 'Bar\\C', cannot use for 'Foo\\C'."); + +$namespace->addClass('B'); +Assert::exception(function () use ($namespace) { + $namespace->addUse('Lorem\B', 'B'); +}, Nette\InvalidStateException::class, "Alias 'B' used already for 'Foo\\B', cannot use for 'Lorem\\B'."); + +Assert::same(['C' => 'Bar\\C', 'B' => 'Foo\\B'], $namespace->getUses()); + + +// alias generation +$namespace = new PhpNamespace(''); +$namespace->addUse('C'); +Assert::same('C', $namespace->simplifyName('C')); +$namespace->addUse('Bar\C'); +Assert::same('BarC', $namespace->simplifyName('Bar\C')); + +$namespace = new PhpNamespace(''); +$namespace->addUse('Bar\C'); +Assert::exception(function () use ($namespace) { + $namespace->addUse('C'); +}, Nette\InvalidStateException::class, "Alias 'C' used already for 'Bar\\C', cannot use for 'C'."); + +$namespace = new PhpNamespace(''); +$namespace->addUse('A'); +Assert::same('A', $namespace->simplifyName('A')); +$namespace->addUse('Bar\A'); +Assert::same('BarA', $namespace->simplifyName('Bar\A')); + +$namespace = new PhpNamespace('Foo'); +$namespace->addUse('C'); +Assert::same('C', $namespace->simplifyName('C')); +$namespace->addUse('Bar\C'); +Assert::same('BarC', $namespace->simplifyName('Bar\C')); +Assert::same('C', $namespace->simplifyName('Foo\C')); +Assert::exception(function () use ($namespace) { + $namespace->addUse('Foo\C'); +}, Nette\InvalidStateException::class, "Alias 'C' used already for 'C', cannot use for 'Foo\\C'."); + +$namespace = new PhpNamespace('Foo'); +$namespace->addUse('Bar\C'); +$namespace->addUse('C'); +Assert::same('C1', $namespace->simplifyName('C')); diff --git a/tests/PhpGenerator/PhpNamespace.phpt b/tests/PhpGenerator/PhpNamespace.phpt index 4bf78e49..cabba8bf 100644 --- a/tests/PhpGenerator/PhpNamespace.phpt +++ b/tests/PhpGenerator/PhpNamespace.phpt @@ -1,9 +1,5 @@ getName()); -Assert::same('A', $namespace->simplifyName('A')); -Assert::same('foo\A', $namespace->simplifyName('foo\A')); - -$namespace->addUse('Bar\C'); - -Assert::same('Bar', $namespace->simplifyName('Bar')); -Assert::same('C', $namespace->simplifyName('bar\C')); -Assert::same('C\D', $namespace->simplifyName('Bar\C\D')); - -foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { - Assert::same($type, $namespace->simplifyName($type)); -} - $namespace = new PhpNamespace('Foo'); - Assert::same('Foo', $namespace->getName()); -Assert::same('\A', $namespace->simplifyName('\A')); -Assert::same('\A', $namespace->simplifyName('A')); -Assert::same('\A\\', $namespace->simplifyName('A\\')); -Assert::same('A', $namespace->simplifyName('foo\A')); -Assert::same('A\\', $namespace->simplifyName('foo\A\\')); - -Assert::same('A', $namespace->simplifyType('foo\A')); -Assert::same('null|A', $namespace->simplifyType('null|foo\A')); -Assert::same('?A', $namespace->simplifyType('?foo\A')); -Assert::same('A&\Countable', $namespace->simplifyType('foo\A&Countable')); -Assert::same('', $namespace->simplifyType('')); - -$namespace->addUse('Foo'); -Assert::same('B', $namespace->simplifyName('Foo\B')); - -$namespace->addUse('Bar\C'); -Assert::same(['C' => 'Bar\C', 'Foo' => 'Foo'], $namespace->getUses()); - -Assert::same('\Bar', $namespace->simplifyName('Bar')); -Assert::same('C', $namespace->simplifyName('\bar\C')); -Assert::same('C', $namespace->simplifyName('bar\C')); -Assert::same('C\D', $namespace->simplifyName('Bar\C\D')); -Assert::same('A', $namespace->simplifyType('foo\A<\bar\C, Bar\C\D>')); -Assert::same('žluťoučký', $namespace->simplifyType('foo\žluťoučký')); - -foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { - Assert::same($type, $namespace->simplifyName($type)); -} - $classA = $namespace->addClass('A'); Assert::same($namespace, $classA->getNamespace()); @@ -70,27 +22,3 @@ Assert::same($namespace, $interfaceB->getNamespace()); Assert::count(2, $namespace->getClasses()); Assert::type(Nette\PhpGenerator\ClassType::class, $namespace->getClasses()['A']); - -Assert::exception(function () use ($namespace) { - $traitC = $namespace->addTrait('C'); - Assert::same($namespace, $traitC->getNamespace()); -}, Nette\InvalidStateException::class, "Alias 'C' used already for 'Bar\\C', cannot use for 'Foo\\C'."); - -$classA - ->addImplement('Foo\A') - ->addImplement('Bar\C') - ->addTrait('Bar\D') - ->addAttribute('Foo\A'); - -$method = $classA->addMethod('test'); -$method->addAttribute('Foo\A'); -$method->setReturnType('static|Foo\A'); - -$method->addParameter('a')->setType('Bar\C')->addAttribute('Bar\D'); -$method->addParameter('b')->setType('self'); -$method->addParameter('c')->setType('parent'); -$method->addParameter('d')->setType('array'); -$method->addParameter('e')->setType('?callable'); -$method->addParameter('f')->setType('Bar\C|string'); - -sameFile(__DIR__ . '/expected/PhpNamespace.expect', (string) $namespace); diff --git a/tests/PhpGenerator/PhpNamespace.print.phpt b/tests/PhpGenerator/PhpNamespace.print.phpt new file mode 100644 index 00000000..59e3a1c4 --- /dev/null +++ b/tests/PhpGenerator/PhpNamespace.print.phpt @@ -0,0 +1,35 @@ +addUse('Foo'); +$namespace->addUse('Bar\C'); + +$classA = $namespace->addClass('A'); +$interfaceB = $namespace->addInterface('B'); + +$classA + ->addImplement('Foo\A') + ->addImplement('Bar\C') + ->addTrait('Bar\D') + ->addAttribute('Foo\A'); + +$method = $classA->addMethod('test'); +$method->addAttribute('Foo\A'); +$method->setReturnType('static|Foo\A'); + +$method->addParameter('a')->setType('Bar\C')->addAttribute('Bar\D'); +$method->addParameter('b')->setType('self'); +$method->addParameter('c')->setType('parent'); +$method->addParameter('d')->setType('array'); +$method->addParameter('e')->setType('?callable'); +$method->addParameter('f')->setType('Bar\C|string'); + +sameFile(__DIR__ . '/expected/PhpNamespace.expect', (string) $namespace); From da083007596836b146597b00fc3d807646218fab Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 14 Oct 2021 19:47:28 +0200 Subject: [PATCH 063/266] PhpNamespace: $uses -> $aliases --- src/PhpGenerator/PhpNamespace.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 32275036..23f526db 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -38,7 +38,7 @@ final class PhpNamespace private $bracketedSyntax = false; /** @var string[] */ - private $uses = []; + private $aliases = []; /** @var ClassType[] */ private $classes = []; @@ -114,18 +114,18 @@ public function addUse(string $name, string $alias = null, string &$aliasOut = n } else { $alias = array_pop($path) . $alias; } - } while (isset($this->uses[$alias . $counter]) && $this->uses[$alias . $counter] !== $name); + } while (isset($this->aliases[$alias . $counter]) && $this->aliases[$alias . $counter] !== $name); $alias .= $counter; - } elseif (isset($this->uses[$alias]) && $this->uses[$alias] !== $name) { + } elseif (isset($this->aliases[$alias]) && $this->aliases[$alias] !== $name) { throw new InvalidStateException( - "Alias '$alias' used already for '{$this->uses[$alias]}', cannot use for '$name'." + "Alias '$alias' used already for '{$this->aliases[$alias]}', cannot use for '$name'." ); } $aliasOut = $alias; - $this->uses[$alias] = $name; - asort($this->uses); + $this->aliases[$alias] = $name; + asort($this->aliases); return $this; } @@ -133,7 +133,7 @@ public function addUse(string $name, string $alias = null, string &$aliasOut = n /** @return string[] */ public function getUses(): array { - return $this->uses; + return $this->aliases; } @@ -161,7 +161,7 @@ public function simplifyName(string $name): string ? substr($name, strlen($this->name) + 1) : null; - foreach ($this->uses as $alias => $original) { + foreach ($this->aliases as $alias => $original) { if (Strings::startsWith($lower . '\\', strtolower($original) . '\\')) { $short = $alias . substr($name, strlen($original)); if (!isset($res) || strlen($res) > strlen($short)) { From 5315b85e2de8344f2164f681af54452277a6f712 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 12 Oct 2021 20:26:21 +0200 Subject: [PATCH 064/266] Extractor::prepareReplacements() iterates backwards --- src/PhpGenerator/Extractor.php | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index bf3e2ed8..23e89825 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -147,19 +147,11 @@ private function prepareReplacements(array $statements): array private function performReplacements(string $s, array $replacements): string { usort($replacements, function ($a, $b) { // sort by position in file - return $a[0] <=> $b[0]; + return $b[0] <=> $a[0]; }); - $correctiveOffset = 0; foreach ($replacements as [$start, $end, $replacement]) { - $replacingStringLength = $end - $start + 1; - $s = substr_replace( - $s, - $replacement, - $correctiveOffset + $start, - $replacingStringLength - ); - $correctiveOffset += strlen($replacement) - $replacingStringLength; + $s = substr_replace($s, $replacement, $start, $end - $start + 1); } return $s; } From 628021a91948943af8e11093eff265e5aec3bbe4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 3 Oct 2021 12:22:57 +0200 Subject: [PATCH 065/266] PhpNamespace::addUse() simplified aliases generation --- src/PhpGenerator/PhpNamespace.php | 12 ++++-------- tests/PhpGenerator/PhpNamespace.aliases.phpt | 6 +++--- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 23f526db..e565b043 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -106,16 +106,12 @@ public function addUse(string $name, string $alias = null, string &$aliasOut = n $alias = Helpers::extractShortName($name); } if ($alias === null) { - $path = explode('\\', $name); + $base = Helpers::extractShortName($name); $counter = null; do { - if (empty($path)) { - $counter++; - } else { - $alias = array_pop($path) . $alias; - } - } while (isset($this->aliases[$alias . $counter]) && $this->aliases[$alias . $counter] !== $name); - $alias .= $counter; + $alias = $base . $counter; + $counter++; + } while (isset($this->aliases[$alias]) && $this->aliases[$alias] !== $name); } elseif (isset($this->aliases[$alias]) && $this->aliases[$alias] !== $name) { throw new InvalidStateException( diff --git a/tests/PhpGenerator/PhpNamespace.aliases.phpt b/tests/PhpGenerator/PhpNamespace.aliases.phpt index a2426215..8f92292c 100644 --- a/tests/PhpGenerator/PhpNamespace.aliases.phpt +++ b/tests/PhpGenerator/PhpNamespace.aliases.phpt @@ -76,7 +76,7 @@ $namespace = new PhpNamespace(''); $namespace->addUse('C'); Assert::same('C', $namespace->simplifyName('C')); $namespace->addUse('Bar\C'); -Assert::same('BarC', $namespace->simplifyName('Bar\C')); +Assert::same('C1', $namespace->simplifyName('Bar\C')); $namespace = new PhpNamespace(''); $namespace->addUse('Bar\C'); @@ -88,13 +88,13 @@ $namespace = new PhpNamespace(''); $namespace->addUse('A'); Assert::same('A', $namespace->simplifyName('A')); $namespace->addUse('Bar\A'); -Assert::same('BarA', $namespace->simplifyName('Bar\A')); +Assert::same('A1', $namespace->simplifyName('Bar\A')); $namespace = new PhpNamespace('Foo'); $namespace->addUse('C'); Assert::same('C', $namespace->simplifyName('C')); $namespace->addUse('Bar\C'); -Assert::same('BarC', $namespace->simplifyName('Bar\C')); +Assert::same('C1', $namespace->simplifyName('Bar\C')); Assert::same('C', $namespace->simplifyName('Foo\C')); Assert::exception(function () use ($namespace) { $namespace->addUse('Foo\C'); From 8a12f1dfba5fb155401ce48762a48a7e41aecc26 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 3 Oct 2021 12:32:03 +0200 Subject: [PATCH 066/266] PhpNamespace::addUse() removed parameter $aliasOut (BC break) --- src/PhpGenerator/PhpNamespace.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index e565b043..6bb88dd7 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -90,7 +90,7 @@ public function getBracketedSyntax(): bool * @throws InvalidStateException * @return static */ - public function addUse(string $name, string $alias = null, string &$aliasOut = null): self + public function addUse(string $name, string $alias = null): self { if ( !Helpers::isNamespaceIdentifier($name, true) @@ -119,7 +119,6 @@ public function addUse(string $name, string $alias = null, string &$aliasOut = n ); } - $aliasOut = $alias; $this->aliases[$alias] = $name; asort($this->aliases); return $this; From 0e283d971b31b10167bd9807c64a43e0aac29ba9 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 13 Oct 2021 02:36:38 +0200 Subject: [PATCH 067/266] PhpNamespace: improved collision checking between class and alias --- src/PhpGenerator/PhpNamespace.php | 12 ++++++------ src/PhpGenerator/Printer.php | 8 +++----- tests/PhpGenerator/PhpFile.phpt | 2 +- tests/PhpGenerator/PhpNamespace.aliases.phpt | 17 +++++++---------- .../expected/PhpFile.globalNamespace.expect | 3 ++- .../expected/Printer.namespace.expect | 1 + .../Printer.namespace.unresolved.expect | 1 + 7 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 6bb88dd7..8550af3d 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -102,25 +102,23 @@ public function addUse(string $name, string $alias = null): self } $name = ltrim($name, '\\'); - if ($alias === null && $this->name === Helpers::extractNamespace($name)) { - $alias = Helpers::extractShortName($name); - } if ($alias === null) { $base = Helpers::extractShortName($name); $counter = null; do { $alias = $base . $counter; $counter++; - } while (isset($this->aliases[$alias]) && $this->aliases[$alias] !== $name); + } while ((isset($this->aliases[$alias]) && $this->aliases[$alias] !== $name) || isset($this->classes[$alias])); } elseif (isset($this->aliases[$alias]) && $this->aliases[$alias] !== $name) { throw new InvalidStateException( "Alias '$alias' used already for '{$this->aliases[$alias]}', cannot use for '$name'." ); + } elseif (isset($this->classes[$alias])) { + throw new Nette\InvalidStateException("Name '$alias' used already for '$this->name\\{$this->classes[$alias]->getName()}'."); } $this->aliases[$alias] = $name; - asort($this->aliases); return $this; } @@ -128,6 +126,7 @@ public function addUse(string $name, string $alias = null): self /** @return string[] */ public function getUses(): array { + asort($this->aliases); return $this->aliases; } @@ -175,8 +174,9 @@ public function add(ClassType $class): self $name = $class->getName(); if ($name === null) { throw new Nette\InvalidArgumentException('Class does not have a name.'); + } elseif ($orig = $this->aliases[$name] ?? null) { + throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig."); } - $this->addUse($this->name . '\\' . $name); $this->classes[$name] = $class; return $this; } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index ed03a98e..5023570e 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -281,11 +281,9 @@ protected function printUses(PhpNamespace $namespace): string $name = $namespace->getName(); $uses = []; foreach ($namespace->getUses() as $alias => $original) { - if ($original !== ($name ? $name . '\\' . $alias : $alias)) { - $uses[] = $alias === $original || substr($original, -(strlen($alias) + 1)) === '\\' . $alias - ? "use $original;" - : "use $original as $alias;"; - } + $uses[] = Helpers::extractShortName($original) === $alias + ? "use $original;" + : "use $original as $alias;"; } return implode("\n", $uses); } diff --git a/tests/PhpGenerator/PhpFile.phpt b/tests/PhpGenerator/PhpFile.phpt index 270277df..e167b52f 100644 --- a/tests/PhpGenerator/PhpFile.phpt +++ b/tests/PhpGenerator/PhpFile.phpt @@ -114,7 +114,7 @@ Assert::same(['Baz\\f2', 'f1'], array_keys($file->getFunctions())); $file = new PhpFile; -$file->addClass('A'); +$file->addClass('CA'); $file->addUse('A') ->addUse('B', 'C'); diff --git a/tests/PhpGenerator/PhpNamespace.aliases.phpt b/tests/PhpGenerator/PhpNamespace.aliases.phpt index 8f92292c..5b12efa6 100644 --- a/tests/PhpGenerator/PhpNamespace.aliases.phpt +++ b/tests/PhpGenerator/PhpNamespace.aliases.phpt @@ -44,7 +44,6 @@ $namespace->addUse('Foo'); Assert::same('B', $namespace->simplifyName('Foo\B')); $namespace->addUse('Bar\C'); -Assert::same(['C' => 'Bar\C', 'Foo' => 'Foo'], $namespace->getUses()); Assert::same('C', $namespace->simplifyName('Foo\C')); Assert::same('\Bar', $namespace->simplifyName('Bar')); @@ -61,14 +60,14 @@ $namespace->addUse('Bar\C'); Assert::exception(function () use ($namespace) { $namespace->addTrait('C'); -}, Nette\InvalidStateException::class, "Alias 'C' used already for 'Bar\\C', cannot use for 'Foo\\C'."); +}, Nette\InvalidStateException::class, "Name 'C' used already as alias for Bar\\C."); $namespace->addClass('B'); Assert::exception(function () use ($namespace) { $namespace->addUse('Lorem\B', 'B'); -}, Nette\InvalidStateException::class, "Alias 'B' used already for 'Foo\\B', cannot use for 'Lorem\\B'."); +}, Nette\InvalidStateException::class, "Name 'B' used already for 'Foo\\B'."); -Assert::same(['C' => 'Bar\\C', 'B' => 'Foo\\B'], $namespace->getUses()); +Assert::same(['C' => 'Bar\\C'], $namespace->getUses()); // alias generation @@ -80,9 +79,8 @@ Assert::same('C1', $namespace->simplifyName('Bar\C')); $namespace = new PhpNamespace(''); $namespace->addUse('Bar\C'); -Assert::exception(function () use ($namespace) { - $namespace->addUse('C'); -}, Nette\InvalidStateException::class, "Alias 'C' used already for 'Bar\\C', cannot use for 'C'."); +$namespace->addUse('C'); +Assert::same('C1', $namespace->simplifyName('C')); $namespace = new PhpNamespace(''); $namespace->addUse('A'); @@ -96,9 +94,8 @@ Assert::same('C', $namespace->simplifyName('C')); $namespace->addUse('Bar\C'); Assert::same('C1', $namespace->simplifyName('Bar\C')); Assert::same('C', $namespace->simplifyName('Foo\C')); -Assert::exception(function () use ($namespace) { - $namespace->addUse('Foo\C'); -}, Nette\InvalidStateException::class, "Alias 'C' used already for 'C', cannot use for 'Foo\\C'."); +$namespace->addUse('Foo\C'); +Assert::same('C', $namespace->simplifyName('Foo\C')); $namespace = new PhpNamespace('Foo'); $namespace->addUse('Bar\C'); diff --git a/tests/PhpGenerator/expected/PhpFile.globalNamespace.expect b/tests/PhpGenerator/expected/PhpFile.globalNamespace.expect index fa8a9acc..606f1b0b 100644 --- a/tests/PhpGenerator/expected/PhpFile.globalNamespace.expect +++ b/tests/PhpGenerator/expected/PhpFile.globalNamespace.expect @@ -1,7 +1,8 @@ Date: Thu, 14 Oct 2021 23:53:47 +0200 Subject: [PATCH 068/266] use-statements for functions & constants --- readme.md | 2 + src/PhpGenerator/Extractor.php | 15 ++-- src/PhpGenerator/Helpers.php | 16 ++-- src/PhpGenerator/PhpFile.php | 4 +- src/PhpGenerator/PhpNamespace.php | 73 ++++++++++++++----- src/PhpGenerator/Printer.php | 21 ++++-- tests/PhpGenerator/PhpNamespace.aliases.phpt | 56 +++++++++++++- tests/PhpGenerator/PhpNamespace.print.phpt | 2 + .../expected/Factory.fromCode.bodies.expect | 2 + .../Factory.fromCode.bodies.resolving.expect | 4 +- .../PhpGenerator/expected/PhpNamespace.expect | 2 + tests/PhpGenerator/fixtures/bodies.php | 2 + 12 files changed, 151 insertions(+), 48 deletions(-) diff --git a/readme.md b/readme.md index 583d09d2..f9ffb765 100644 --- a/readme.md +++ b/readme.md @@ -582,6 +582,8 @@ You can define use-statements: $namespace->addUse(Http\Request::class); // use Http\Request as HttpReq; $namespace->addUse(Http\Request::class, 'HttpReq'); +// use function iter\range; +$namespace->addUseFunction('iter\range'); ``` Class Names Resolving diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 23e89825..84b61065 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -102,13 +102,13 @@ private function prepareReplacements(array $statements): array (new NodeFinder)->find($statements, function (Node $node) use (&$replacements, $start) { if ($node instanceof Node\Name\FullyQualified) { if ($node->getAttribute('originalName') instanceof Node\Name) { - $type = $node->getAttribute('parent') instanceof Node\Expr\ConstFetch + $of = $node->getAttribute('parent') instanceof Node\Expr\ConstFetch ? PhpNamespace::NAME_CONSTANT : ($node->getAttribute('parent') instanceof Node\Expr\FuncCall ? PhpNamespace::NAME_FUNCTION : PhpNamespace::NAME_NORMAL); $replacements[] = [ $node->getStartFilePos() - $start, $node->getEndFilePos() - $start, - Helpers::tagName($node->toCodeString(), $type), + Helpers::tagName($node->toCodeString(), $of), ]; } @@ -213,10 +213,13 @@ public function enterNode(Node $node) private function addUseToNamespace(Node\Stmt\Use_ $node, PhpNamespace $namespace): void { - if ($node->type === $node::TYPE_NORMAL) { - foreach ($node->uses as $use) { - $namespace->addUse($use->name->toString(), $use->alias ? $use->alias->toString() : null); - } + $of = [ + $node::TYPE_NORMAL => PhpNamespace::NAME_NORMAL, + $node::TYPE_FUNCTION => PhpNamespace::NAME_FUNCTION, + $node::TYPE_CONSTANT => PhpNamespace::NAME_CONSTANT, + ][$node->type]; + foreach ($node->uses as $use) { + $namespace->addUse($use->name->toString(), $use->alias ? $use->alias->toString() : null, $of); } } diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 2de0d1f1..c533e4f7 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -83,25 +83,21 @@ public static function formatDocComment(string $content): string } - public static function tagName(string $name, string $type = PhpNamespace::NAME_NORMAL): string + public static function tagName(string $name, string $of = PhpNamespace::NAME_NORMAL): string { return isset(self::KEYWORDS[strtolower($name)]) ? $name - : "/*($type*/$name"; + : "/*($of*/$name"; } public static function simplifyTaggedNames(string $code, ?PhpNamespace $namespace): string { return preg_replace_callback('~/\*\(([ncf])\*/([\w\x7f-\xff\\\\]++)~', function ($m) use ($namespace) { - [, $type, $name] = $m; - if (!$namespace) { - return $name; - } elseif ($type === PhpNamespace::NAME_NORMAL) { - return $namespace->simplifyType($name); - } else { - return $namespace->simplifyType(self::extractNamespace($name) . '\\') . self::extractShortName($name); - } + [, $of, $name] = $m; + return $namespace + ? $namespace->simplifyType($name, $of) + : $name; }, $code); } diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index ce3cd02c..04964659 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -134,9 +134,9 @@ public function getFunctions(): array /** @return static */ - public function addUse(string $name, string $alias = null): self + public function addUse(string $name, string $alias = null, string $of = PhpNamespace::NAME_NORMAL): self { - $this->addNamespace('')->addUse($name, $alias); + $this->addNamespace('')->addUse($name, $alias, $of); return $this; } diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 8550af3d..f8fd4ad2 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -37,8 +37,12 @@ final class PhpNamespace /** @var bool */ private $bracketedSyntax = false; - /** @var string[] */ - private $aliases = []; + /** @var string[][] */ + private $aliases = [ + self::NAME_NORMAL => [], + self::NAME_FUNCTION => [], + self::NAME_CONSTANT => [], + ]; /** @var ClassType[] */ private $classes = []; @@ -90,44 +94,62 @@ public function getBracketedSyntax(): bool * @throws InvalidStateException * @return static */ - public function addUse(string $name, string $alias = null): self + public function addUse(string $name, string $alias = null, string $of = self::NAME_NORMAL): self { if ( !Helpers::isNamespaceIdentifier($name, true) || (Helpers::isIdentifier($name) && isset(Helpers::KEYWORDS[strtolower($name)])) ) { - throw new Nette\InvalidArgumentException("Value '$name' is not valid class name."); + throw new Nette\InvalidArgumentException("Value '$name' is not valid class/function/constant name."); + } elseif ($alias && (!Helpers::isIdentifier($alias) || isset(Helpers::KEYWORDS[strtolower($alias)]))) { throw new Nette\InvalidArgumentException("Value '$alias' is not valid alias."); } $name = ltrim($name, '\\'); + $aliases = $this->aliases[$of]; + $used = [self::NAME_NORMAL => $this->classes, self::NAME_FUNCTION => $this->functions, self::NAME_CONSTANT => []][$of]; + if ($alias === null) { $base = Helpers::extractShortName($name); $counter = null; do { $alias = $base . $counter; $counter++; - } while ((isset($this->aliases[$alias]) && $this->aliases[$alias] !== $name) || isset($this->classes[$alias])); + } while ((isset($aliases[$alias]) && $aliases[$alias] !== $name) || isset($used[$alias])); - } elseif (isset($this->aliases[$alias]) && $this->aliases[$alias] !== $name) { + } elseif (isset($aliases[$alias]) && $aliases[$alias] !== $name) { throw new InvalidStateException( - "Alias '$alias' used already for '{$this->aliases[$alias]}', cannot use for '$name'." + "Alias '$alias' used already for '{$aliases[$alias]}', cannot use for '$name'." ); - } elseif (isset($this->classes[$alias])) { - throw new Nette\InvalidStateException("Name '$alias' used already for '$this->name\\{$this->classes[$alias]->getName()}'."); + } elseif (isset($used[$alias])) { + throw new Nette\InvalidStateException("Name '$alias' used already for '$this->name\\{$used[$alias]->getName()}'."); } - $this->aliases[$alias] = $name; + $this->aliases[$of][$alias] = $name; return $this; } + /** @return static */ + public function addUseFunction(string $name, string $alias = null): self + { + return $this->addUse($name, $alias, self::NAME_FUNCTION); + } + + + /** @return static */ + public function addUseConstant(string $name, string $alias = null): self + { + return $this->addUse($name, $alias, self::NAME_CONSTANT); + } + + /** @return string[] */ - public function getUses(): array + public function getUses(string $of = self::NAME_NORMAL): array { - asort($this->aliases); - return $this->aliases; + asort($this->aliases[$of]); + return $this->aliases[$of]; } @@ -138,24 +160,34 @@ public function unresolveName(string $name): string } - public function simplifyType(string $type): string + public function simplifyType(string $type, string $of = self::NAME_NORMAL): string { - return preg_replace_callback('~[\w\x7f-\xff\\\\]+~', function ($m) { return $this->simplifyName($m[0]); }, $type); + return preg_replace_callback('~[\w\x7f-\xff\\\\]+~', function ($m) use ($of) { return $this->simplifyName($m[0], $of); }, $type); } - public function simplifyName(string $name): string + public function simplifyName(string $name, string $of = self::NAME_NORMAL): string { if (isset(Helpers::KEYWORDS[strtolower($name)]) || $name === '') { return $name; } $name = ltrim($name, '\\'); + + if ($of !== self::NAME_NORMAL) { + foreach ($this->aliases[$of] as $alias => $original) { + if (strcasecmp($original, $name) === 0) { + return $alias; + } + } + return $this->simplifyName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name); + } + $lower = strtolower($name); $res = Strings::startsWith($lower, strtolower($this->name) . '\\') ? substr($name, strlen($this->name) + 1) : null; - foreach ($this->aliases as $alias => $original) { + foreach ($this->aliases[$of] as $alias => $original) { if (Strings::startsWith($lower . '\\', strtolower($original) . '\\')) { $short = $alias . substr($name, strlen($original)); if (!isset($res) || strlen($res) > strlen($short)) { @@ -164,7 +196,7 @@ public function simplifyName(string $name): string } } - return $res ?: ($this->name ? '\\' : '') . $name; + return $res ?? ($this->name ? '\\' : '') . $name; } @@ -174,7 +206,7 @@ public function add(ClassType $class): self $name = $class->getName(); if ($name === null) { throw new Nette\InvalidArgumentException('Class does not have a name.'); - } elseif ($orig = $this->aliases[$name] ?? null) { + } elseif ($orig = $this->aliases[self::NAME_NORMAL][$name] ?? null) { throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig."); } $this->classes[$name] = $class; @@ -209,6 +241,9 @@ public function addEnum(string $name): ClassType public function addFunction(string $name): GlobalFunction { + if ($orig = $this->aliases[self::NAME_FUNCTION][$name] ?? null) { + throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig."); + } return $this->functions[$name] = new GlobalFunction($name); } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 5023570e..112f22ad 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -234,7 +234,9 @@ public function printNamespace(PhpNamespace $namespace): string { $this->namespace = $this->resolveTypes ? $namespace : null; $name = $namespace->getName(); - $uses = $this->printUses($namespace); + $uses = $this->printUses($namespace) + . $this->printUses($namespace, PhpNamespace::NAME_FUNCTION) + . $this->printUses($namespace, PhpNamespace::NAME_CONSTANT); $items = []; foreach ($namespace->getClasses() as $class) { @@ -244,7 +246,7 @@ public function printNamespace(PhpNamespace $namespace): string $items[] = $this->printFunction($function, $namespace); } - $body = ($uses ? $uses . "\n\n" : '') + $body = ($uses ? $uses . "\n" : '') . implode("\n", $items); if ($namespace->hasBracketedSyntax()) { @@ -276,16 +278,21 @@ public function printFile(PhpFile $file): string } - protected function printUses(PhpNamespace $namespace): string + protected function printUses(PhpNamespace $namespace, string $of = PhpNamespace::NAME_NORMAL): string { + $prefix = [ + PhpNamespace::NAME_NORMAL => '', + PhpNamespace::NAME_FUNCTION => 'function ', + PhpNamespace::NAME_CONSTANT => 'const ', + ][$of]; $name = $namespace->getName(); $uses = []; - foreach ($namespace->getUses() as $alias => $original) { + foreach ($namespace->getUses($of) as $alias => $original) { $uses[] = Helpers::extractShortName($original) === $alias - ? "use $original;" - : "use $original as $alias;"; + ? "use $prefix$original;\n" + : "use $prefix$original as $alias;\n"; } - return implode("\n", $uses); + return implode('', $uses); } diff --git a/tests/PhpGenerator/PhpNamespace.aliases.phpt b/tests/PhpGenerator/PhpNamespace.aliases.phpt index 5b12efa6..09a73013 100644 --- a/tests/PhpGenerator/PhpNamespace.aliases.phpt +++ b/tests/PhpGenerator/PhpNamespace.aliases.phpt @@ -18,13 +18,36 @@ Assert::same('foo\A', $namespace->simplifyName('foo\A')); $namespace->addUse('Bar\C'); Assert::same('Bar', $namespace->simplifyName('Bar')); -Assert::same('C', $namespace->simplifyName('bar\C')); +Assert::same('C', $namespace->simplifyName('Bar\C')); Assert::same('C\D', $namespace->simplifyName('Bar\C\D')); foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { Assert::same($type, $namespace->simplifyName($type)); } +$namespace->addUseFunction('Foo\a'); + +Assert::same('Bar\c', $namespace->simplifyName('Bar\c', $namespace::NAME_FUNCTION)); +Assert::same('a', $namespace->simplifyName('Foo\A', $namespace::NAME_FUNCTION)); +Assert::same('Foo\a\b', $namespace->simplifyName('Foo\a\b', $namespace::NAME_FUNCTION)); + +foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { + Assert::same($type, $namespace->simplifyName($type, $namespace::NAME_FUNCTION)); +} + +$namespace->addUseFunction('Bar\c'); + +Assert::same('Bar', $namespace->simplifyName('Bar', $namespace::NAME_FUNCTION)); +Assert::same('c', $namespace->simplifyName('Bar\c', $namespace::NAME_FUNCTION)); +Assert::same('C\d', $namespace->simplifyName('Bar\C\d', $namespace::NAME_FUNCTION)); + +$namespace->addUseConstant('Bar\c'); + +Assert::same('Bar', $namespace->simplifyName('Bar', $namespace::NAME_CONSTANT)); +Assert::same('c', $namespace->simplifyName('Bar\c', $namespace::NAME_CONSTANT)); +Assert::same('C\d', $namespace->simplifyName('Bar\C\d', $namespace::NAME_CONSTANT)); + + // namespace $namespace = new PhpNamespace('Foo'); @@ -53,6 +76,19 @@ Assert::same('C\D', $namespace->simplifyName('Bar\C\D')); Assert::same('A', $namespace->simplifyType('foo\A<\bar\C, Bar\C\D>')); Assert::same('žluťoučký', $namespace->simplifyType('foo\žluťoučký')); +$namespace->addUseFunction('Foo\a'); + +Assert::same('\Bar\c', $namespace->simplifyName('Bar\c', $namespace::NAME_FUNCTION)); +Assert::same('a', $namespace->simplifyName('Foo\a', $namespace::NAME_FUNCTION)); +Assert::same('C\b', $namespace->simplifyName('Foo\C\b', $namespace::NAME_FUNCTION)); +Assert::same('a\b', $namespace->simplifyName('Foo\a\b', $namespace::NAME_FUNCTION)); + +$namespace->addUseFunction('Bar\c'); + +Assert::same('\Bar', $namespace->simplifyName('Bar', $namespace::NAME_FUNCTION)); +Assert::same('c', $namespace->simplifyName('Bar\c', $namespace::NAME_FUNCTION)); +Assert::same('C\d', $namespace->simplifyName('Bar\c\d', $namespace::NAME_FUNCTION)); + // duplicity $namespace = new PhpNamespace('Foo'); @@ -67,7 +103,18 @@ Assert::exception(function () use ($namespace) { $namespace->addUse('Lorem\B', 'B'); }, Nette\InvalidStateException::class, "Name 'B' used already for 'Foo\\B'."); -Assert::same(['C' => 'Bar\\C'], $namespace->getUses()); +$namespace->addUseFunction('Bar\f1'); +Assert::exception(function () use ($namespace) { + $namespace->addFunction('f1'); +}, Nette\InvalidStateException::class, "Name 'f1' used already as alias for Bar\\f1."); + +$namespace->addFunction('f2'); +Assert::exception(function () use ($namespace) { + $namespace->addUseFunction('Bar\f2', 'f2'); +}, Nette\InvalidStateException::class, "Name 'f2' used already for 'Foo\\f2'."); + +Assert::same(['C' => 'Bar\C'], $namespace->getUses()); +Assert::same(['f1' => 'Bar\f1'], $namespace->getUses($namespace::NAME_FUNCTION)); // alias generation @@ -101,3 +148,8 @@ $namespace = new PhpNamespace('Foo'); $namespace->addUse('Bar\C'); $namespace->addUse('C'); Assert::same('C1', $namespace->simplifyName('C')); + +$namespace = new PhpNamespace('Foo'); +$namespace->addUseFunction('Bar\c'); +$namespace->addUseFunction('c'); +Assert::same('c1', $namespace->simplifyName('c', $namespace::NAME_FUNCTION)); diff --git a/tests/PhpGenerator/PhpNamespace.print.phpt b/tests/PhpGenerator/PhpNamespace.print.phpt index 59e3a1c4..96351893 100644 --- a/tests/PhpGenerator/PhpNamespace.print.phpt +++ b/tests/PhpGenerator/PhpNamespace.print.phpt @@ -11,6 +11,8 @@ $namespace = new PhpNamespace('Foo'); $namespace->addUse('Foo'); $namespace->addUse('Bar\C'); +$namespace->addUseFunction('Bar\c'); +$namespace->addUseConstant('Bar\FOO'); $classA = $namespace->addClass('A'); $interfaceB = $namespace->addInterface('B'); diff --git a/tests/PhpGenerator/expected/Factory.fromCode.bodies.expect b/tests/PhpGenerator/expected/Factory.fromCode.bodies.expect index 4ec14cae..aaa0a3ff 100644 --- a/tests/PhpGenerator/expected/Factory.fromCode.bodies.expect +++ b/tests/PhpGenerator/expected/Factory.fromCode.bodies.expect @@ -5,6 +5,8 @@ declare(strict_types=1); namespace Abc; use Nette; +use function substr; +use const BAR; abstract class Class7 { diff --git a/tests/PhpGenerator/expected/Factory.fromCode.bodies.resolving.expect b/tests/PhpGenerator/expected/Factory.fromCode.bodies.resolving.expect index b9f4cdc1..d2e1236f 100644 --- a/tests/PhpGenerator/expected/Factory.fromCode.bodies.resolving.expect +++ b/tests/PhpGenerator/expected/Factory.fromCode.bodies.resolving.expect @@ -47,13 +47,13 @@ abstract class Class7 echo FOO; echo \FOO; echo \Abc\a\FOO; - echo \Nette\FOO; + echo FOO; // functions func(); \func(); \Abc\a\func(); - \Nette\func(); + func(); // classes $x = new \Abc\MyClass; diff --git a/tests/PhpGenerator/expected/PhpNamespace.expect b/tests/PhpGenerator/expected/PhpNamespace.expect index 3e451749..23e26d2b 100644 --- a/tests/PhpGenerator/expected/PhpNamespace.expect +++ b/tests/PhpGenerator/expected/PhpNamespace.expect @@ -2,6 +2,8 @@ namespace Foo; use Bar\C; use Foo; +use function Bar\c; +use const Bar\FOO; #[A] class A implements A, C diff --git a/tests/PhpGenerator/fixtures/bodies.php b/tests/PhpGenerator/fixtures/bodies.php index 1d917efe..99bc0a57 100644 --- a/tests/PhpGenerator/fixtures/bodies.php +++ b/tests/PhpGenerator/fixtures/bodies.php @@ -5,6 +5,8 @@ namespace Abc; use Nette; +use function substr; +use const BAR; abstract class Class7 { From 5a39bcfc816cce8f2a9aefbbf74a130196976236 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 14 Oct 2021 23:54:20 +0200 Subject: [PATCH 069/266] PhpNamespace: class and function names are case-insensitive --- src/PhpGenerator/PhpNamespace.php | 36 ++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index f8fd4ad2..931abdd4 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -116,14 +116,17 @@ public function addUse(string $name, string $alias = null, string $of = self::NA do { $alias = $base . $counter; $counter++; - } while ((isset($aliases[$alias]) && $aliases[$alias] !== $name) || isset($used[$alias])); - - } elseif (isset($aliases[$alias]) && $aliases[$alias] !== $name) { - throw new InvalidStateException( - "Alias '$alias' used already for '{$aliases[$alias]}', cannot use for '$name'." - ); - } elseif (isset($used[$alias])) { - throw new Nette\InvalidStateException("Name '$alias' used already for '$this->name\\{$used[$alias]->getName()}'."); + } while ((isset($aliases[$alias]) && $aliases[$alias] !== $name) || isset($used[strtolower($alias)])); + + } else { + $lower = strtolower($alias); + if (isset($aliases[$alias]) && $aliases[$alias] !== $name) { + throw new InvalidStateException( + "Alias '$alias' used already for '{$aliases[$alias]}', cannot use for '$name'." + ); + } elseif (isset($used[$lower])) { + throw new Nette\InvalidStateException("Name '$alias' used already for '$this->name\\{$used[$lower]->getName()}'."); + } } $this->aliases[$of][$alias] = $name; @@ -209,7 +212,7 @@ public function add(ClassType $class): self } elseif ($orig = $this->aliases[self::NAME_NORMAL][$name] ?? null) { throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig."); } - $this->classes[$name] = $class; + $this->classes[strtolower($name)] = $class; return $this; } @@ -241,24 +244,33 @@ public function addEnum(string $name): ClassType public function addFunction(string $name): GlobalFunction { + $lower = strtolower($name); if ($orig = $this->aliases[self::NAME_FUNCTION][$name] ?? null) { throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig."); } - return $this->functions[$name] = new GlobalFunction($name); + return $this->functions[$lower] = new GlobalFunction($name); } /** @return ClassType[] */ public function getClasses(): array { - return $this->classes; + $res = []; + foreach ($this->classes as $class) { + $res[$class->getName()] = $class; + } + return $res; } /** @return GlobalFunction[] */ public function getFunctions(): array { - return $this->functions; + $res = []; + foreach ($this->functions as $fn) { + $res[$fn->getName()] = $fn; + } + return $res; } From 90082c1ad91b55a48b04b5fbd6d9d462ae6d6f72 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 14 Oct 2021 19:04:15 +0200 Subject: [PATCH 070/266] PhpNamespace: addUse() & simplifyName() is case-insensitive --- src/PhpGenerator/PhpNamespace.php | 29 ++++++++------ tests/PhpGenerator/PhpNamespace.aliases.phpt | 40 ++++++++++++++------ 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 931abdd4..daaf6498 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -11,7 +11,6 @@ use Nette; use Nette\InvalidStateException; -use Nette\Utils\Strings; /** @@ -107,7 +106,7 @@ public function addUse(string $name, string $alias = null, string $of = self::NA } $name = ltrim($name, '\\'); - $aliases = $this->aliases[$of]; + $aliases = array_change_key_case($this->aliases[$of]); $used = [self::NAME_NORMAL => $this->classes, self::NAME_FUNCTION => $this->functions, self::NAME_CONSTANT => []][$of]; if ($alias === null) { @@ -115,14 +114,15 @@ public function addUse(string $name, string $alias = null, string $of = self::NA $counter = null; do { $alias = $base . $counter; + $lower = strtolower($alias); $counter++; - } while ((isset($aliases[$alias]) && $aliases[$alias] !== $name) || isset($used[strtolower($alias)])); + } while ((isset($aliases[$lower]) && strcasecmp($aliases[$lower], $name) !== 0) || isset($used[$lower])); } else { $lower = strtolower($alias); - if (isset($aliases[$alias]) && $aliases[$alias] !== $name) { + if (isset($aliases[$lower]) && strcasecmp($aliases[$lower], $name) !== 0) { throw new InvalidStateException( - "Alias '$alias' used already for '{$aliases[$alias]}', cannot use for '$name'." + "Alias '$alias' used already for '{$aliases[$lower]}', cannot use for '$name'." ); } elseif (isset($used[$lower])) { throw new Nette\InvalidStateException("Name '$alias' used already for '$this->name\\{$used[$lower]->getName()}'."); @@ -185,13 +185,12 @@ public function simplifyName(string $name, string $of = self::NAME_NORMAL): stri return $this->simplifyName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name); } - $lower = strtolower($name); - $res = Strings::startsWith($lower, strtolower($this->name) . '\\') + $res = self::startsWith($name, $this->name . '\\') ? substr($name, strlen($this->name) + 1) : null; foreach ($this->aliases[$of] as $alias => $original) { - if (Strings::startsWith($lower . '\\', strtolower($original) . '\\')) { + if (self::startsWith($name . '\\', $original . '\\')) { $short = $alias . substr($name, strlen($original)); if (!isset($res) || strlen($res) > strlen($short)) { $res = $short; @@ -209,10 +208,12 @@ public function add(ClassType $class): self $name = $class->getName(); if ($name === null) { throw new Nette\InvalidArgumentException('Class does not have a name.'); - } elseif ($orig = $this->aliases[self::NAME_NORMAL][$name] ?? null) { + } + $lower = strtolower($name); + if ($orig = array_change_key_case($this->aliases[self::NAME_NORMAL])[$lower] ?? null) { throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig."); } - $this->classes[strtolower($name)] = $class; + $this->classes[$lower] = $class; return $this; } @@ -245,7 +246,7 @@ public function addEnum(string $name): ClassType public function addFunction(string $name): GlobalFunction { $lower = strtolower($name); - if ($orig = $this->aliases[self::NAME_FUNCTION][$name] ?? null) { + if ($orig = array_change_key_case($this->aliases[self::NAME_FUNCTION])[$lower] ?? null) { throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig."); } return $this->functions[$lower] = new GlobalFunction($name); @@ -274,6 +275,12 @@ public function getFunctions(): array } + private static function startsWith(string $a, string $b): bool + { + return strncasecmp($a, $b, strlen($b)) === 0; + } + + public function __toString(): string { try { diff --git a/tests/PhpGenerator/PhpNamespace.aliases.phpt b/tests/PhpGenerator/PhpNamespace.aliases.phpt index 09a73013..818aa347 100644 --- a/tests/PhpGenerator/PhpNamespace.aliases.phpt +++ b/tests/PhpGenerator/PhpNamespace.aliases.phpt @@ -18,7 +18,7 @@ Assert::same('foo\A', $namespace->simplifyName('foo\A')); $namespace->addUse('Bar\C'); Assert::same('Bar', $namespace->simplifyName('Bar')); -Assert::same('C', $namespace->simplifyName('Bar\C')); +Assert::same('C', $namespace->simplifyName('bar\C')); Assert::same('C\D', $namespace->simplifyName('Bar\C\D')); foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { @@ -27,9 +27,9 @@ foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self $namespace->addUseFunction('Foo\a'); -Assert::same('Bar\c', $namespace->simplifyName('Bar\c', $namespace::NAME_FUNCTION)); -Assert::same('a', $namespace->simplifyName('Foo\A', $namespace::NAME_FUNCTION)); -Assert::same('Foo\a\b', $namespace->simplifyName('Foo\a\b', $namespace::NAME_FUNCTION)); +Assert::same('bar\c', $namespace->simplifyName('bar\c', $namespace::NAME_FUNCTION)); +Assert::same('a', $namespace->simplifyName('foo\A', $namespace::NAME_FUNCTION)); +Assert::same('foo\a\b', $namespace->simplifyName('foo\a\b', $namespace::NAME_FUNCTION)); foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { Assert::same($type, $namespace->simplifyName($type, $namespace::NAME_FUNCTION)); @@ -38,13 +38,13 @@ foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self $namespace->addUseFunction('Bar\c'); Assert::same('Bar', $namespace->simplifyName('Bar', $namespace::NAME_FUNCTION)); -Assert::same('c', $namespace->simplifyName('Bar\c', $namespace::NAME_FUNCTION)); +Assert::same('c', $namespace->simplifyName('bar\c', $namespace::NAME_FUNCTION)); Assert::same('C\d', $namespace->simplifyName('Bar\C\d', $namespace::NAME_FUNCTION)); $namespace->addUseConstant('Bar\c'); Assert::same('Bar', $namespace->simplifyName('Bar', $namespace::NAME_CONSTANT)); -Assert::same('c', $namespace->simplifyName('Bar\c', $namespace::NAME_CONSTANT)); +Assert::same('c', $namespace->simplifyName('bar\c', $namespace::NAME_CONSTANT)); Assert::same('C\d', $namespace->simplifyName('Bar\C\d', $namespace::NAME_CONSTANT)); @@ -78,16 +78,16 @@ Assert::same('žluťoučký', $namespace->simplifyType('foo\žluťoučký')); $namespace->addUseFunction('Foo\a'); -Assert::same('\Bar\c', $namespace->simplifyName('Bar\c', $namespace::NAME_FUNCTION)); -Assert::same('a', $namespace->simplifyName('Foo\a', $namespace::NAME_FUNCTION)); -Assert::same('C\b', $namespace->simplifyName('Foo\C\b', $namespace::NAME_FUNCTION)); -Assert::same('a\b', $namespace->simplifyName('Foo\a\b', $namespace::NAME_FUNCTION)); +Assert::same('\bar\c', $namespace->simplifyName('bar\c', $namespace::NAME_FUNCTION)); +Assert::same('a', $namespace->simplifyName('foo\A', $namespace::NAME_FUNCTION)); +Assert::same('C\b', $namespace->simplifyName('foo\C\b', $namespace::NAME_FUNCTION)); +Assert::same('a\b', $namespace->simplifyName('foo\a\b', $namespace::NAME_FUNCTION)); $namespace->addUseFunction('Bar\c'); Assert::same('\Bar', $namespace->simplifyName('Bar', $namespace::NAME_FUNCTION)); -Assert::same('c', $namespace->simplifyName('Bar\c', $namespace::NAME_FUNCTION)); -Assert::same('C\d', $namespace->simplifyName('Bar\c\d', $namespace::NAME_FUNCTION)); +Assert::same('c', $namespace->simplifyName('bar\c', $namespace::NAME_FUNCTION)); +Assert::same('C\d', $namespace->simplifyName('Bar\C\d', $namespace::NAME_FUNCTION)); // duplicity @@ -98,21 +98,37 @@ Assert::exception(function () use ($namespace) { $namespace->addTrait('C'); }, Nette\InvalidStateException::class, "Name 'C' used already as alias for Bar\\C."); +Assert::exception(function () use ($namespace) { + $namespace->addTrait('c'); +}, Nette\InvalidStateException::class, "Name 'c' used already as alias for Bar\\C."); + $namespace->addClass('B'); Assert::exception(function () use ($namespace) { $namespace->addUse('Lorem\B', 'B'); }, Nette\InvalidStateException::class, "Name 'B' used already for 'Foo\\B'."); +Assert::exception(function () use ($namespace) { + $namespace->addUse('lorem\b', 'b'); +}, Nette\InvalidStateException::class, "Name 'b' used already for 'Foo\\B'."); + $namespace->addUseFunction('Bar\f1'); Assert::exception(function () use ($namespace) { $namespace->addFunction('f1'); }, Nette\InvalidStateException::class, "Name 'f1' used already as alias for Bar\\f1."); +Assert::exception(function () use ($namespace) { + $namespace->addFunction('F1'); +}, Nette\InvalidStateException::class, "Name 'F1' used already as alias for Bar\\f1."); + $namespace->addFunction('f2'); Assert::exception(function () use ($namespace) { $namespace->addUseFunction('Bar\f2', 'f2'); }, Nette\InvalidStateException::class, "Name 'f2' used already for 'Foo\\f2'."); +Assert::exception(function () use ($namespace) { + $namespace->addUseFunction('Bar\f2', 'F2'); +}, Nette\InvalidStateException::class, "Name 'F2' used already for 'Foo\\f2'."); + Assert::same(['C' => 'Bar\C'], $namespace->getUses()); Assert::same(['f1' => 'Bar\f1'], $namespace->getUses($namespace::NAME_FUNCTION)); From 3db5decb51dc7d3c07a1061421349a5c96bf0924 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 13 Oct 2021 02:24:07 +0200 Subject: [PATCH 071/266] ClassType: method names are case insensitive --- src/PhpGenerator/ClassType.php | 23 +++++++++++++-------- tests/PhpGenerator/ClassType.addMember.phpt | 1 + tests/PhpGenerator/ClassType.phpt | 5 +++-- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 4f8205b6..fe092a2f 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -395,7 +395,7 @@ public function addMember($member): self if ($this->isInterface()) { $member->setBody(null); } - $this->methods[$member->getName()] = $member; + $this->methods[strtolower($member->getName())] = $member; } elseif ($member instanceof Property) { $this->properties[$member->getName()] = $member; @@ -563,8 +563,8 @@ public function setMethods(array $methods): self { (function (Method ...$methods) {})(...array_values($methods)); $this->methods = []; - foreach ($methods as $v) { - $this->methods[$v->getName()] = $v; + foreach ($methods as $m) { + $this->methods[strtolower($m->getName())] = $m; } return $this; } @@ -573,16 +573,21 @@ public function setMethods(array $methods): self /** @return Method[] */ public function getMethods(): array { - return $this->methods; + $res = []; + foreach ($this->methods as $m) { + $res[$m->getName()] = $m; + } + return $res; } public function getMethod(string $name): Method { - if (!isset($this->methods[$name])) { + $m = $this->methods[strtolower($name)] ?? null; + if (!$m) { throw new Nette\InvalidArgumentException("Method '$name' not found."); } - return $this->methods[$name]; + return $m; } @@ -594,21 +599,21 @@ public function addMethod(string $name): Method } else { $method->setPublic(); } - return $this->methods[$name] = $method; + return $this->methods[strtolower($name)] = $method; } /** @return static */ public function removeMethod(string $name): self { - unset($this->methods[$name]); + unset($this->methods[strtolower($name)]); return $this; } public function hasMethod(string $name): bool { - return isset($this->methods[$name]); + return isset($this->methods[strtolower($name)]); } diff --git a/tests/PhpGenerator/ClassType.addMember.phpt b/tests/PhpGenerator/ClassType.addMember.phpt index 16e92350..0486303c 100644 --- a/tests/PhpGenerator/ClassType.addMember.phpt +++ b/tests/PhpGenerator/ClassType.addMember.phpt @@ -16,6 +16,7 @@ Assert::exception(function () { $class = (new ClassType('Example')) + ->addMember($method = new Nette\PhpGenerator\Method('GETHANDLE')) ->addMember($method = new Nette\PhpGenerator\Method('getHandle')) ->addMember($property = new Nette\PhpGenerator\Property('handle')) ->addMember($const = new Nette\PhpGenerator\Constant('ROLE')) diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index 625e9e53..b29ac616 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -186,7 +186,8 @@ $class->removeProperty('b')->removeProperty('c'); Assert::same(['a'], array_keys($class->getProperties())); $class->addMethod('a'); +$class->addMethod('A'); $class->addMethod('b'); -$class->removeMethod('b')->removeMethod('c'); +$class->removeMethod('B')->removeMethod('c'); -Assert::same(['a'], array_keys($class->getMethods())); +Assert::same(['A'], array_keys($class->getMethods())); From 2592c8f7544bb8e1e83ae6d2dd7b511db5e14023 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 13 Oct 2021 18:40:18 +0200 Subject: [PATCH 072/266] PhpNamespace::simplifyName() fixed collision when alias already exists --- src/PhpGenerator/PhpNamespace.php | 16 ++++++++++++---- tests/PhpGenerator/PhpNamespace.aliases.phpt | 8 ++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index daaf6498..3066af9f 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -185,20 +185,28 @@ public function simplifyName(string $name, string $of = self::NAME_NORMAL): stri return $this->simplifyName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name); } - $res = self::startsWith($name, $this->name . '\\') + $shortest = null; + $relative = self::startsWith($name, $this->name . '\\') ? substr($name, strlen($this->name) + 1) : null; foreach ($this->aliases[$of] as $alias => $original) { + if ($relative && self::startsWith($relative . '\\', $alias . '\\')) { + $relative = null; + } if (self::startsWith($name . '\\', $original . '\\')) { $short = $alias . substr($name, strlen($original)); - if (!isset($res) || strlen($res) > strlen($short)) { - $res = $short; + if (!isset($shortest) || strlen($shortest) > strlen($short)) { + $shortest = $short; } } } - return $res ?? ($this->name ? '\\' : '') . $name; + if (isset($shortest, $relative) && strlen($shortest) < strlen($relative)) { + return $relative; + } + + return $relative ?? $shortest ?? ($this->name ? '\\' : '') . $name; } diff --git a/tests/PhpGenerator/PhpNamespace.aliases.phpt b/tests/PhpGenerator/PhpNamespace.aliases.phpt index 818aa347..85556b33 100644 --- a/tests/PhpGenerator/PhpNamespace.aliases.phpt +++ b/tests/PhpGenerator/PhpNamespace.aliases.phpt @@ -67,7 +67,7 @@ $namespace->addUse('Foo'); Assert::same('B', $namespace->simplifyName('Foo\B')); $namespace->addUse('Bar\C'); -Assert::same('C', $namespace->simplifyName('Foo\C')); +Assert::same('Foo\C', $namespace->simplifyName('Foo\C')); Assert::same('\Bar', $namespace->simplifyName('Bar')); Assert::same('C', $namespace->simplifyName('\bar\C')); @@ -80,7 +80,7 @@ $namespace->addUseFunction('Foo\a'); Assert::same('\bar\c', $namespace->simplifyName('bar\c', $namespace::NAME_FUNCTION)); Assert::same('a', $namespace->simplifyName('foo\A', $namespace::NAME_FUNCTION)); -Assert::same('C\b', $namespace->simplifyName('foo\C\b', $namespace::NAME_FUNCTION)); +Assert::same('Foo\C\b', $namespace->simplifyName('foo\C\b', $namespace::NAME_FUNCTION)); Assert::same('a\b', $namespace->simplifyName('foo\a\b', $namespace::NAME_FUNCTION)); $namespace->addUseFunction('Bar\c'); @@ -156,9 +156,9 @@ $namespace->addUse('C'); Assert::same('C', $namespace->simplifyName('C')); $namespace->addUse('Bar\C'); Assert::same('C1', $namespace->simplifyName('Bar\C')); -Assert::same('C', $namespace->simplifyName('Foo\C')); +Assert::same('\Foo\C', $namespace->simplifyName('Foo\C')); $namespace->addUse('Foo\C'); -Assert::same('C', $namespace->simplifyName('Foo\C')); +Assert::same('C2', $namespace->simplifyName('Foo\C')); $namespace = new PhpNamespace('Foo'); $namespace->addUse('Bar\C'); From fa374ae7b36be45241fd0b5acd49479a6d1227a3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 15 Oct 2021 00:10:38 +0200 Subject: [PATCH 073/266] updated workflows --- .github/workflows/static-analysis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index b0692d71..36203715 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -1,9 +1,6 @@ name: Static Analysis (only informative) -on: - push: - branches: - - master +on: [push, pull_request] jobs: phpstan: From b8375ac20760c62b6816f8c2eaeabbbca305eed7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 15 Oct 2021 12:27:41 +0200 Subject: [PATCH 074/266] PhpNamespace::simplifyName() fixed relative path evaluation [Closes #94] --- src/PhpGenerator/PhpNamespace.php | 2 +- tests/PhpGenerator/PhpNamespace.aliases.phpt | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 3066af9f..cbca0fc1 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -203,7 +203,7 @@ public function simplifyName(string $name, string $of = self::NAME_NORMAL): stri } if (isset($shortest, $relative) && strlen($shortest) < strlen($relative)) { - return $relative; + return $shortest; } return $relative ?? $shortest ?? ($this->name ? '\\' : '') . $name; diff --git a/tests/PhpGenerator/PhpNamespace.aliases.phpt b/tests/PhpGenerator/PhpNamespace.aliases.phpt index 85556b33..1665d095 100644 --- a/tests/PhpGenerator/PhpNamespace.aliases.phpt +++ b/tests/PhpGenerator/PhpNamespace.aliases.phpt @@ -165,6 +165,10 @@ $namespace->addUse('Bar\C'); $namespace->addUse('C'); Assert::same('C1', $namespace->simplifyName('C')); +$namespace = new PhpNamespace('Foo\Bar'); +$namespace->addUse('Foo\Bar\Baz\Qux'); +Assert::same('Qux', $namespace->simplifyName('Foo\Bar\Baz\Qux')); + $namespace = new PhpNamespace('Foo'); $namespace->addUseFunction('Bar\c'); $namespace->addUseFunction('c'); From 6268076f57004885d0d1d9e9034030137a788d41 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 22 Nov 2021 23:32:27 +0100 Subject: [PATCH 075/266] renamed some tests --- tests/PhpGenerator/Extractor.extractAll.phpt | 14 +-- .../Extractor.extractAll.resolving.phpt | 4 +- tests/PhpGenerator/PhpFile.phpt | 2 +- tests/PhpGenerator/PhpNamespace.aliases.phpt | 82 ----------------- tests/PhpGenerator/PhpNamespace.simplify.phpt | 90 +++++++++++++++++++ ....bodies.expect => Extractor.bodies.expect} | 0 ...pect => Extractor.bodies.resolving.expect} | 0 ...ct => Extractor.bodies.unresolving.expect} | 0 ....74.expect => Extractor.classes.74.expect} | 0 ....80.expect => Extractor.classes.80.expect} | 0 ....81.expect => Extractor.classes.81.expect} | 0 ...omCode.expect => Extractor.classes.expect} | 0 ...Code.enum.expect => Extractor.enum.expect} | 0 ....traits.expect => Extractor.traits.expect} | 0 14 files changed, 100 insertions(+), 92 deletions(-) create mode 100644 tests/PhpGenerator/PhpNamespace.simplify.phpt rename tests/PhpGenerator/expected/{Factory.fromCode.bodies.expect => Extractor.bodies.expect} (100%) rename tests/PhpGenerator/expected/{Factory.fromCode.bodies.resolving.expect => Extractor.bodies.resolving.expect} (100%) rename tests/PhpGenerator/expected/{Factory.fromCode.bodies.unresolving.expect => Extractor.bodies.unresolving.expect} (100%) rename tests/PhpGenerator/expected/{Factory.fromCode.74.expect => Extractor.classes.74.expect} (100%) rename tests/PhpGenerator/expected/{Factory.fromCode.80.expect => Extractor.classes.80.expect} (100%) rename tests/PhpGenerator/expected/{Factory.fromCode.81.expect => Extractor.classes.81.expect} (100%) rename tests/PhpGenerator/expected/{Factory.fromCode.expect => Extractor.classes.expect} (100%) rename tests/PhpGenerator/expected/{Factory.fromCode.enum.expect => Extractor.enum.expect} (100%) rename tests/PhpGenerator/expected/{Factory.fromCode.traits.expect => Extractor.traits.expect} (100%) diff --git a/tests/PhpGenerator/Extractor.extractAll.phpt b/tests/PhpGenerator/Extractor.extractAll.phpt index 713b7332..c680d860 100644 --- a/tests/PhpGenerator/Extractor.extractAll.phpt +++ b/tests/PhpGenerator/Extractor.extractAll.phpt @@ -10,25 +10,25 @@ require __DIR__ . '/../bootstrap.php'; $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.php')))->extractAll(); Assert::type(Nette\PhpGenerator\PhpFile::class, $file); -sameFile(__DIR__ . '/expected/Factory.fromCode.expect', (string) $file); +sameFile(__DIR__ . '/expected/Extractor.classes.expect', (string) $file); $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.74.php')))->extractAll(); -sameFile(__DIR__ . '/expected/Factory.fromCode.74.expect', (string) $file); +sameFile(__DIR__ . '/expected/Extractor.classes.74.expect', (string) $file); $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.80.php')))->extractAll(); -sameFile(__DIR__ . '/expected/Factory.fromCode.80.expect', (string) $file); +sameFile(__DIR__ . '/expected/Extractor.classes.80.expect', (string) $file); $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.81.php')))->extractAll(); -sameFile(__DIR__ . '/expected/Factory.fromCode.81.expect', (string) $file); +sameFile(__DIR__ . '/expected/Extractor.classes.81.expect', (string) $file); $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/enum.php')))->extractAll(); -sameFile(__DIR__ . '/expected/Factory.fromCode.enum.expect', (string) $file); +sameFile(__DIR__ . '/expected/Extractor.enum.expect', (string) $file); $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/traits.php')))->extractAll(); -sameFile(__DIR__ . '/expected/Factory.fromCode.traits.expect', (string) $file); +sameFile(__DIR__ . '/expected/Extractor.traits.expect', (string) $file); $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/bodies.php')))->extractAll(); -sameFile(__DIR__ . '/expected/Factory.fromCode.bodies.expect', (string) $file); +sameFile(__DIR__ . '/expected/Extractor.bodies.expect', (string) $file); $file = (new Extractor( <<<'XX' diff --git a/tests/PhpGenerator/Extractor.extractAll.resolving.phpt b/tests/PhpGenerator/Extractor.extractAll.resolving.phpt index c927f541..65d720ea 100644 --- a/tests/PhpGenerator/Extractor.extractAll.resolving.phpt +++ b/tests/PhpGenerator/Extractor.extractAll.resolving.phpt @@ -18,7 +18,7 @@ $namespace->addUse('Abc\a\func'); // must not be confused with func $namespace->add(reset($classes)); $printer = new Printer; -sameFile(__DIR__ . '/expected/Factory.fromCode.bodies.resolving.expect', $printer->printNamespace($namespace)); +sameFile(__DIR__ . '/expected/Extractor.bodies.resolving.expect', $printer->printNamespace($namespace)); $printer->setTypeResolving(false); -sameFile(__DIR__ . '/expected/Factory.fromCode.bodies.unresolving.expect', $printer->printNamespace($namespace)); +sameFile(__DIR__ . '/expected/Extractor.bodies.unresolving.expect', $printer->printNamespace($namespace)); diff --git a/tests/PhpGenerator/PhpFile.phpt b/tests/PhpGenerator/PhpFile.phpt index e167b52f..4280ef15 100644 --- a/tests/PhpGenerator/PhpFile.phpt +++ b/tests/PhpGenerator/PhpFile.phpt @@ -133,4 +133,4 @@ sameFile(__DIR__ . '/expected/PhpFile.strictTypes.expect', (string) $file); $file = PhpFile::fromCode(file_get_contents(__DIR__ . '/fixtures/classes.php')); Assert::type(PhpFile::class, $file); -sameFile(__DIR__ . '/expected/Factory.fromCode.expect', (string) $file); +sameFile(__DIR__ . '/expected/Extractor.classes.expect', (string) $file); diff --git a/tests/PhpGenerator/PhpNamespace.aliases.phpt b/tests/PhpGenerator/PhpNamespace.aliases.phpt index 1665d095..1e217f83 100644 --- a/tests/PhpGenerator/PhpNamespace.aliases.phpt +++ b/tests/PhpGenerator/PhpNamespace.aliases.phpt @@ -8,88 +8,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -// global namespace -$namespace = new PhpNamespace(''); - -Assert::same('', $namespace->getName()); -Assert::same('A', $namespace->simplifyName('A')); -Assert::same('foo\A', $namespace->simplifyName('foo\A')); - -$namespace->addUse('Bar\C'); - -Assert::same('Bar', $namespace->simplifyName('Bar')); -Assert::same('C', $namespace->simplifyName('bar\C')); -Assert::same('C\D', $namespace->simplifyName('Bar\C\D')); - -foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { - Assert::same($type, $namespace->simplifyName($type)); -} - -$namespace->addUseFunction('Foo\a'); - -Assert::same('bar\c', $namespace->simplifyName('bar\c', $namespace::NAME_FUNCTION)); -Assert::same('a', $namespace->simplifyName('foo\A', $namespace::NAME_FUNCTION)); -Assert::same('foo\a\b', $namespace->simplifyName('foo\a\b', $namespace::NAME_FUNCTION)); - -foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { - Assert::same($type, $namespace->simplifyName($type, $namespace::NAME_FUNCTION)); -} - -$namespace->addUseFunction('Bar\c'); - -Assert::same('Bar', $namespace->simplifyName('Bar', $namespace::NAME_FUNCTION)); -Assert::same('c', $namespace->simplifyName('bar\c', $namespace::NAME_FUNCTION)); -Assert::same('C\d', $namespace->simplifyName('Bar\C\d', $namespace::NAME_FUNCTION)); - -$namespace->addUseConstant('Bar\c'); - -Assert::same('Bar', $namespace->simplifyName('Bar', $namespace::NAME_CONSTANT)); -Assert::same('c', $namespace->simplifyName('bar\c', $namespace::NAME_CONSTANT)); -Assert::same('C\d', $namespace->simplifyName('Bar\C\d', $namespace::NAME_CONSTANT)); - - - -// namespace -$namespace = new PhpNamespace('Foo'); - -Assert::same('Foo', $namespace->getName()); -Assert::same('\A', $namespace->simplifyName('\A')); -Assert::same('\A', $namespace->simplifyName('A')); -Assert::same('A', $namespace->simplifyName('foo\A')); - -Assert::same('A', $namespace->simplifyType('foo\A')); -Assert::same('null|A', $namespace->simplifyType('null|foo\A')); -Assert::same('?A', $namespace->simplifyType('?foo\A')); -Assert::same('A&\Countable', $namespace->simplifyType('foo\A&Countable')); -Assert::same('', $namespace->simplifyType('')); - -$namespace->addUse('Foo'); -Assert::same('B', $namespace->simplifyName('Foo\B')); - -$namespace->addUse('Bar\C'); -Assert::same('Foo\C', $namespace->simplifyName('Foo\C')); - -Assert::same('\Bar', $namespace->simplifyName('Bar')); -Assert::same('C', $namespace->simplifyName('\bar\C')); -Assert::same('C', $namespace->simplifyName('bar\C')); -Assert::same('C\D', $namespace->simplifyName('Bar\C\D')); -Assert::same('A', $namespace->simplifyType('foo\A<\bar\C, Bar\C\D>')); -Assert::same('žluťoučký', $namespace->simplifyType('foo\žluťoučký')); - -$namespace->addUseFunction('Foo\a'); - -Assert::same('\bar\c', $namespace->simplifyName('bar\c', $namespace::NAME_FUNCTION)); -Assert::same('a', $namespace->simplifyName('foo\A', $namespace::NAME_FUNCTION)); -Assert::same('Foo\C\b', $namespace->simplifyName('foo\C\b', $namespace::NAME_FUNCTION)); -Assert::same('a\b', $namespace->simplifyName('foo\a\b', $namespace::NAME_FUNCTION)); - -$namespace->addUseFunction('Bar\c'); - -Assert::same('\Bar', $namespace->simplifyName('Bar', $namespace::NAME_FUNCTION)); -Assert::same('c', $namespace->simplifyName('bar\c', $namespace::NAME_FUNCTION)); -Assert::same('C\d', $namespace->simplifyName('Bar\C\d', $namespace::NAME_FUNCTION)); - - // duplicity $namespace = new PhpNamespace('Foo'); $namespace->addUse('Bar\C'); diff --git a/tests/PhpGenerator/PhpNamespace.simplify.phpt b/tests/PhpGenerator/PhpNamespace.simplify.phpt new file mode 100644 index 00000000..ca978a5a --- /dev/null +++ b/tests/PhpGenerator/PhpNamespace.simplify.phpt @@ -0,0 +1,90 @@ +getName()); +Assert::same('A', $namespace->simplifyName('A')); +Assert::same('foo\A', $namespace->simplifyName('foo\A')); + +$namespace->addUse('Bar\C'); + +Assert::same('Bar', $namespace->simplifyName('Bar')); +Assert::same('C', $namespace->simplifyName('bar\C')); +Assert::same('C\D', $namespace->simplifyName('Bar\C\D')); + +$namespace->addUseFunction('Foo\a'); + +Assert::same('bar\c', $namespace->simplifyName('bar\c', $namespace::NAME_FUNCTION)); +Assert::same('a', $namespace->simplifyName('foo\A', $namespace::NAME_FUNCTION)); +Assert::same('foo\a\b', $namespace->simplifyName('foo\a\b', $namespace::NAME_FUNCTION)); + +$namespace->addUseFunction('Bar\c'); + +Assert::same('Bar', $namespace->simplifyName('Bar', $namespace::NAME_FUNCTION)); +Assert::same('c', $namespace->simplifyName('bar\c', $namespace::NAME_FUNCTION)); +Assert::same('C\d', $namespace->simplifyName('Bar\C\d', $namespace::NAME_FUNCTION)); + +$namespace->addUseConstant('Bar\c'); + +Assert::same('Bar', $namespace->simplifyName('Bar', $namespace::NAME_CONSTANT)); +Assert::same('c', $namespace->simplifyName('bar\c', $namespace::NAME_CONSTANT)); +Assert::same('C\d', $namespace->simplifyName('Bar\C\d', $namespace::NAME_CONSTANT)); + + + +// namespace +$namespace = new PhpNamespace('Foo'); + +foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { + Assert::same($type, $namespace->simplifyName($type)); +} + +Assert::same('Foo', $namespace->getName()); +Assert::same('\A', $namespace->simplifyName('\A')); +Assert::same('\A', $namespace->simplifyName('A')); +Assert::same('A', $namespace->simplifyName('foo\A')); + +Assert::same('A', $namespace->simplifyType('foo\A')); +Assert::same('null|A', $namespace->simplifyType('null|foo\A')); +Assert::same('?A', $namespace->simplifyType('?foo\A')); +Assert::same('A&\Countable', $namespace->simplifyType('foo\A&Countable')); +Assert::same('', $namespace->simplifyType('')); + +$namespace->addUse('Foo'); +Assert::same('B', $namespace->simplifyName('Foo\B')); + +$namespace->addUse('Bar\C'); +Assert::same('Foo\C', $namespace->simplifyName('Foo\C')); + +Assert::same('\Bar', $namespace->simplifyName('Bar')); +Assert::same('C', $namespace->simplifyName('\bar\C')); +Assert::same('C', $namespace->simplifyName('bar\C')); +Assert::same('C\D', $namespace->simplifyName('Bar\C\D')); +Assert::same('A', $namespace->simplifyType('foo\A<\bar\C, Bar\C\D>')); +Assert::same('žluťoučký', $namespace->simplifyType('foo\žluťoučký')); + +$namespace->addUseFunction('Foo\a'); + +foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { + Assert::same($type, $namespace->simplifyName($type, $namespace::NAME_FUNCTION)); +} + +Assert::same('\bar\c', $namespace->simplifyName('bar\c', $namespace::NAME_FUNCTION)); +Assert::same('a', $namespace->simplifyName('foo\A', $namespace::NAME_FUNCTION)); +Assert::same('Foo\C\b', $namespace->simplifyName('foo\C\b', $namespace::NAME_FUNCTION)); +Assert::same('a\b', $namespace->simplifyName('foo\a\b', $namespace::NAME_FUNCTION)); + +$namespace->addUseFunction('Bar\c'); + +Assert::same('\Bar', $namespace->simplifyName('Bar', $namespace::NAME_FUNCTION)); +Assert::same('c', $namespace->simplifyName('bar\c', $namespace::NAME_FUNCTION)); +Assert::same('C\d', $namespace->simplifyName('Bar\C\d', $namespace::NAME_FUNCTION)); diff --git a/tests/PhpGenerator/expected/Factory.fromCode.bodies.expect b/tests/PhpGenerator/expected/Extractor.bodies.expect similarity index 100% rename from tests/PhpGenerator/expected/Factory.fromCode.bodies.expect rename to tests/PhpGenerator/expected/Extractor.bodies.expect diff --git a/tests/PhpGenerator/expected/Factory.fromCode.bodies.resolving.expect b/tests/PhpGenerator/expected/Extractor.bodies.resolving.expect similarity index 100% rename from tests/PhpGenerator/expected/Factory.fromCode.bodies.resolving.expect rename to tests/PhpGenerator/expected/Extractor.bodies.resolving.expect diff --git a/tests/PhpGenerator/expected/Factory.fromCode.bodies.unresolving.expect b/tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect similarity index 100% rename from tests/PhpGenerator/expected/Factory.fromCode.bodies.unresolving.expect rename to tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect diff --git a/tests/PhpGenerator/expected/Factory.fromCode.74.expect b/tests/PhpGenerator/expected/Extractor.classes.74.expect similarity index 100% rename from tests/PhpGenerator/expected/Factory.fromCode.74.expect rename to tests/PhpGenerator/expected/Extractor.classes.74.expect diff --git a/tests/PhpGenerator/expected/Factory.fromCode.80.expect b/tests/PhpGenerator/expected/Extractor.classes.80.expect similarity index 100% rename from tests/PhpGenerator/expected/Factory.fromCode.80.expect rename to tests/PhpGenerator/expected/Extractor.classes.80.expect diff --git a/tests/PhpGenerator/expected/Factory.fromCode.81.expect b/tests/PhpGenerator/expected/Extractor.classes.81.expect similarity index 100% rename from tests/PhpGenerator/expected/Factory.fromCode.81.expect rename to tests/PhpGenerator/expected/Extractor.classes.81.expect diff --git a/tests/PhpGenerator/expected/Factory.fromCode.expect b/tests/PhpGenerator/expected/Extractor.classes.expect similarity index 100% rename from tests/PhpGenerator/expected/Factory.fromCode.expect rename to tests/PhpGenerator/expected/Extractor.classes.expect diff --git a/tests/PhpGenerator/expected/Factory.fromCode.enum.expect b/tests/PhpGenerator/expected/Extractor.enum.expect similarity index 100% rename from tests/PhpGenerator/expected/Factory.fromCode.enum.expect rename to tests/PhpGenerator/expected/Extractor.enum.expect diff --git a/tests/PhpGenerator/expected/Factory.fromCode.traits.expect b/tests/PhpGenerator/expected/Extractor.traits.expect similarity index 100% rename from tests/PhpGenerator/expected/Factory.fromCode.traits.expect rename to tests/PhpGenerator/expected/Extractor.traits.expect From fc9795960bc0b0188ce45bee113091a2cfdbde9a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 23 Nov 2021 00:14:42 +0100 Subject: [PATCH 076/266] test: sameFile() changes output name --- tests/bootstrap.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index a826bf40..9754ccbe 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -20,7 +20,12 @@ function same(string $expected, $actual): void function sameFile(string $file, $actual): void { - same(file_get_contents($file), $actual); + try { + same(file_get_contents($file), $actual); + } catch (Tester\AssertException $e) { + $e->outputName = basename($file, '.expect'); + throw $e; + } } From 19941d712878b4d6a485500135c37e9bd66f23af Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Nov 2021 21:40:04 +0100 Subject: [PATCH 077/266] Dumper: improved encoding of strings --- src/PhpGenerator/Dumper.php | 53 ++++++++++++++++++--------- tests/PhpGenerator/Dumper.dump().phpt | 6 ++- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 9f2e0af8..01fd0972 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -64,25 +64,44 @@ private function dumpVar(&$var, array $parents = [], int $level = 0, int $column } - private function dumpString(string $var): string + private function dumpString(string $s): string { - if (preg_match('#[^\x09\x20-\x7E\xA0-\x{10FFFF}]#u', $var) || preg_last_error()) { - static $table; - if ($table === null) { - foreach (array_merge(range("\x00", "\x1F"), range("\x7F", "\xFF")) as $ch) { - $table[$ch] = '\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT); - } - $table['\\'] = '\\\\'; - $table["\r"] = '\r'; - $table["\n"] = '\n'; - $table["\t"] = '\t'; - $table['$'] = '\$'; - $table['"'] = '\"'; - } - return '"' . strtr($var, $table) . '"'; - } + static $special = [ + "\r" => '\r', + "\n" => '\n', + "\t" => '\t', + "\e" => '\e', + '\\' => '\\\\', + ]; + + $utf8 = preg_match('##u', $s); + $escaped = preg_replace_callback( + $utf8 ? '#[\p{C}\\\\]#u' : '#[\x00-\x1F\x7F-\xFF\\\\]#', + function ($m) use ($special) { + return $special[$m[0]] ?? (strlen($m[0]) === 1 + ? '\x' . str_pad(strtoupper(dechex(ord($m[0]))), 2, '0', STR_PAD_LEFT) . '' + : '\u{' . strtoupper(ltrim(dechex(self::utf8Ord($m[0])), '0')) . '}'); + }, + $s + ); + return $s === str_replace('\\\\', '\\', $escaped) + ? "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|$)#D', '\\\\$0', $s) . "'" + : '"' . addcslashes($escaped, '"$') . '"'; + } - return "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|$)#D', '\\\\$0', $var) . "'"; + + private static function utf8Ord(string $c): int + { + $ord0 = ord($c[0]); + if ($ord0 < 0x80) { + return $ord0; + } elseif ($ord0 < 0xE0) { + return ($ord0 << 6) + ord($c[1]) - 0x3080; + } elseif ($ord0 < 0xF0) { + return ($ord0 << 12) + (ord($c[1]) << 6) + ord($c[2]) - 0xE2080; + } else { + return ($ord0 << 18) + (ord($c[1]) << 12) + (ord($c[2]) << 6) + ord($c[3]) - 0x3C82080; + } } diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index fbcc62db..be0ca966 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -32,7 +32,11 @@ Assert::same('false', $dumper->dump(false)); Assert::same("''", $dumper->dump('')); Assert::same("'Hello'", $dumper->dump('Hello')); -Assert::same('"\t\n\t"', $dumper->dump("\t\n\t")); +Assert::same('"\t\n\r\e"', $dumper->dump("\t\n\r\e")); +Assert::same('"\u{FEFF}"', $dumper->dump("\xEF\xBB\xBF")); // BOM +Assert::same('\'$"\\\\\'', $dumper->dump('$"\\')); +Assert::same('\'$"\\ \x00\'', $dumper->dump('$"\\ \x00')); // no escape +Assert::same('"\\$\\"\\\\ \x00"', $dumper->dump("$\"\\ \x00")); Assert::same( "'I\u{F1}t\u{EB}rn\u{E2}ti\u{F4}n\u{E0}liz\u{E6}ti\u{F8}n'", $dumper->dump("I\u{F1}t\u{EB}rn\u{E2}ti\u{F4}n\u{E0}liz\u{E6}ti\u{F8}n") // Iñtërnâtiônàlizætiøn From e973ad0d45efe8ffc817e8358be9ad1a59493e3d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 16 Nov 2021 15:34:00 +0100 Subject: [PATCH 078/266] PhpNamespace::addUse() ignores names in current namespace [Closes #96] --- src/PhpGenerator/PhpNamespace.php | 6 +++++- tests/PhpGenerator/expected/PhpFile.globalNamespace.expect | 1 - tests/PhpGenerator/expected/Printer.namespace.expect | 1 - .../expected/Printer.namespace.unresolved.expect | 1 - 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index cbca0fc1..71946f75 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -152,7 +152,11 @@ public function addUseConstant(string $name, string $alias = null): self public function getUses(string $of = self::NAME_NORMAL): array { asort($this->aliases[$of]); - return $this->aliases[$of]; + return array_filter( + $this->aliases[$of], + function ($name, $alias) { return strcasecmp(($this->name ? $this->name . '\\' : '') . $alias, $name); }, + ARRAY_FILTER_USE_BOTH + ); } diff --git a/tests/PhpGenerator/expected/PhpFile.globalNamespace.expect b/tests/PhpGenerator/expected/PhpFile.globalNamespace.expect index 606f1b0b..396ce925 100644 --- a/tests/PhpGenerator/expected/PhpFile.globalNamespace.expect +++ b/tests/PhpGenerator/expected/PhpFile.globalNamespace.expect @@ -1,6 +1,5 @@ Date: Mon, 22 Nov 2021 23:26:35 +0100 Subject: [PATCH 079/266] Extractor: fixed extracting attributes [Closes #98] --- src/PhpGenerator/Extractor.php | 2 +- tests/PhpGenerator/expected/Extractor.classes.80.expect | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 84b61065..e330b4d1 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -359,7 +359,7 @@ private function addCommentAndAttributes($element, Node $node): void foreach ($group->attrs as $attribute) { $args = []; foreach ($attribute->args as $arg) { - $value = new Literal($this->getReformattedContents([$arg], 0)); + $value = new Literal($this->getReformattedContents([$arg->value], 0)); if ($arg->name) { $args[$arg->name->toString()] = $value; } else { diff --git a/tests/PhpGenerator/expected/Extractor.classes.80.expect b/tests/PhpGenerator/expected/Extractor.classes.80.expect index d112eca3..7696e495 100644 --- a/tests/PhpGenerator/expected/Extractor.classes.80.expect +++ b/tests/PhpGenerator/expected/Extractor.classes.80.expect @@ -15,7 +15,7 @@ class Class8 * Description of class. */ #[\ExampleAttribute] -#[NamedArguments(foo: foo: 'bar', bar: bar: [1, 2, 3])] +#[NamedArguments(foo: 'bar', bar: [1, 2, 3])] class Class9 { /** Commented */ From 875c9a7338b080d148fef605388d2f8d6ffe537f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 23 Nov 2021 15:24:13 +0100 Subject: [PATCH 080/266] PhpNamespace: added resolveName() --- readme.md | 20 ++++- src/PhpGenerator/PhpNamespace.php | 21 +++++ tests/PhpGenerator/PhpNamespace.resolve.phpt | 86 ++++++++++++++++++++ 3 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 tests/PhpGenerator/PhpNamespace.resolve.phpt diff --git a/readme.md b/readme.md index f9ffb765..d3cfe485 100644 --- a/readme.md +++ b/readme.md @@ -586,6 +586,20 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` +To simplify a fully qualified class, function or constant name according to the defined aliases, use the `simplifyName` method: + +```php +echo $namespace->simplifyName('Foo\Bar'); // 'Bar', because 'Foo' is current namespace +echo $namespace->simplifyName('iter\range', $namespace::NAME_FUNCTION); // 'range', because of the defined use-statement +``` + +Conversely, you can convert a simplified class, function or constant name to a fully qualified one using the `resolveName` method: + +```php +echo $namespace->resolveName('Bar'); // 'Foo\Bar' +echo $namespace->resolveName('range', $namespace::NAME_FUNCTION); // 'iter\range' +``` + Class Names Resolving --------------------- @@ -599,11 +613,11 @@ $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); -$class->addImplement('Foo\A') // it will resolve to A - ->addTrait('Bar\AliasedClass'); // it will resolve to AliasedClass +$class->addImplement('Foo\A') // it will simplify to A + ->addTrait('Bar\AliasedClass'); // it will simplify to AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // in comments resolve manually +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // in comments simplify manually $method->addParameter('arg') ->setType('Bar\OtherClass'); // it will resolve to \Bar\OtherClass diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 71946f75..6348175d 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -167,6 +167,27 @@ public function unresolveName(string $name): string } + public function resolveName(string $name, string $of = self::NAME_NORMAL): string + { + if (isset(Helpers::KEYWORDS[strtolower($name)]) || $name === '') { + return $name; + } elseif ($name[0] === '\\') { + return substr($name, 1); + } + + $aliases = array_change_key_case($this->aliases[$of]); + if ($of !== self::NAME_NORMAL) { + return $aliases[strtolower($name)] + ?? $this->resolveName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name); + } + + $parts = explode('\\', $name, 2); + return ($res = $aliases[strtolower($parts[0])] ?? null) + ? $res . (isset($parts[1]) ? '\\' . $parts[1] : '') + : $this->name . ($this->name ? '\\' : '') . $name; + } + + public function simplifyType(string $type, string $of = self::NAME_NORMAL): string { return preg_replace_callback('~[\w\x7f-\xff\\\\]+~', function ($m) use ($of) { return $this->simplifyName($m[0], $of); }, $type); diff --git a/tests/PhpGenerator/PhpNamespace.resolve.phpt b/tests/PhpGenerator/PhpNamespace.resolve.phpt new file mode 100644 index 00000000..e5b78f1e --- /dev/null +++ b/tests/PhpGenerator/PhpNamespace.resolve.phpt @@ -0,0 +1,86 @@ +getName()); +Assert::same('', $namespace->resolveName('')); +Assert::same('', $namespace->resolveName('\\')); +Assert::same('A', $namespace->resolveName('A')); +Assert::same('A', $namespace->resolveName('\A')); +Assert::same('foo\A', $namespace->resolveName('foo\A')); + +$namespace->addUse('Bar\C'); + +Assert::same('Bar', $namespace->resolveName('Bar')); +Assert::same('Bar\C', $namespace->resolveName('c')); +Assert::same('Bar\C\D', $namespace->resolveName('C\D')); + +$namespace->addUseFunction('Foo\a'); + +Assert::same('bar\c', $namespace->resolveName('bar\c', $namespace::NAME_FUNCTION)); +Assert::same('Foo\a', $namespace->resolveName('A', $namespace::NAME_FUNCTION)); +Assert::same('foo\a\b', $namespace->resolveName('foo\a\b', $namespace::NAME_FUNCTION)); + +$namespace->addUseFunction('Bar\c'); + +Assert::same('Bar', $namespace->resolveName('Bar', $namespace::NAME_FUNCTION)); +Assert::same('Bar\c', $namespace->resolveName('C', $namespace::NAME_FUNCTION)); +Assert::same('Bar\C\d', $namespace->resolveName('c\d', $namespace::NAME_FUNCTION)); + +$namespace->addUseConstant('Bar\c'); + +Assert::same('Bar', $namespace->resolveName('Bar', $namespace::NAME_CONSTANT)); +Assert::same('Bar\c', $namespace->resolveName('C', $namespace::NAME_CONSTANT)); +Assert::same('Bar\C\d', $namespace->resolveName('c\d', $namespace::NAME_CONSTANT)); + + + +// namespace +$namespace = new PhpNamespace('Foo'); + +foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { + Assert::same($type, $namespace->resolveName($type)); +} + +Assert::same('Foo', $namespace->getName()); +Assert::same('', $namespace->resolveName('')); +Assert::same('', $namespace->resolveName('\\')); +Assert::same('A', $namespace->resolveName('\A')); +Assert::same('Foo\A', $namespace->resolveName('A')); + +$namespace->addUse('Foo'); +Assert::same('Foo\B', $namespace->resolveName('B')); + +$namespace->addUse('Bar\C'); +Assert::same('Foo\C', $namespace->resolveName('Foo\C')); + +Assert::same('Bar', $namespace->resolveName('\Bar')); +Assert::same('Bar\C', $namespace->resolveName('C')); +Assert::same('Bar\C', $namespace->resolveName('c')); +Assert::same('Bar\C\D', $namespace->resolveName('c\D')); + +$namespace->addUseFunction('Foo\a'); + +foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { + Assert::same($type, $namespace->resolveName($type, $namespace::NAME_FUNCTION)); +} + +Assert::same('bar\c', $namespace->resolveName('\bar\c', $namespace::NAME_FUNCTION)); +Assert::same('Foo\a', $namespace->resolveName('A', $namespace::NAME_FUNCTION)); +Assert::same('Foo\C\b', $namespace->resolveName('foo\C\b', $namespace::NAME_FUNCTION)); +Assert::same('Foo\A\b', $namespace->resolveName('A\b', $namespace::NAME_FUNCTION)); + +$namespace->addUseFunction('Bar\c'); + +Assert::same('Bar', $namespace->resolveName('\Bar', $namespace::NAME_FUNCTION)); +Assert::same('Bar\c', $namespace->resolveName('C', $namespace::NAME_FUNCTION)); +Assert::same('Bar\C\d', $namespace->resolveName('c\d', $namespace::NAME_FUNCTION)); From 9370403f9d9c25b51c4596ded1fbfe70347f7c82 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 23 Nov 2021 18:16:10 +0100 Subject: [PATCH 081/266] cs --- src/PhpGenerator/Dumper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 01fd0972..3597c1fe 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -207,7 +207,7 @@ private function dumpLiteral(Literal $var, int $level): string /** - * Generates PHP statement. + * Generates PHP statement. Supports placeholders: ? \? $? ->? ::? ...? ...?: ?* */ public function format(string $statement, ...$args): string { From e867b315d0005004055f1aa4fdc584d02d8ceef8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 8 Dec 2021 03:38:35 +0100 Subject: [PATCH 082/266] replaced ecs.php with ncs.php --- ecs.php | 25 ------------------------- ncs.php | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 25 deletions(-) delete mode 100644 ecs.php create mode 100644 ncs.php diff --git a/ecs.php b/ecs.php deleted file mode 100644 index 83fd07b9..00000000 --- a/ecs.php +++ /dev/null @@ -1,25 +0,0 @@ -import(PRESET_DIR . '/php71.php'); - - $parameters = $containerConfigurator->parameters(); - - $parameters->set('skip', [ - 'fixtures*/*', - 'tests/PhpGenerator/Dumper.dump().enum.phpt', // enum - - // constant NULL, FALSE - PhpCsFixer\Fixer\Casing\LowercaseConstantsFixer::class => [ - 'src/PhpGenerator/Type.php', - ], - ]); -}; diff --git a/ncs.php b/ncs.php new file mode 100644 index 00000000..6dca0c3d --- /dev/null +++ b/ncs.php @@ -0,0 +1,14 @@ + false, + 'lowercase_static_reference' => false, +]; From 0892dcf401980458019ea337f4e48b5e4a83e7a9 Mon Sep 17 00:00:00 2001 From: Daan Date: Thu, 25 Nov 2021 10:45:24 +0100 Subject: [PATCH 083/266] typo --- src/PhpGenerator/PhpFile.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index 04964659..31956ead 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -125,8 +125,8 @@ public function getFunctions(): array $functions = []; foreach ($this->namespaces as $n => $namespace) { $n .= $n ? '\\' : ''; - foreach ($namespace->getFunctions() as $c => $class) { - $functions[$n . $c] = $class; + foreach ($namespace->getFunctions() as $f => $function) { + $functions[$n . $f] = $function; } } return $functions; From 4f2d215c0007b98578165c3a522d4202e2a9b4bb Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 8 Dec 2021 03:27:06 +0100 Subject: [PATCH 084/266] cs --- src/PhpGenerator/Helpers.php | 2 +- tests/PhpGenerator/ClassType.from.phpt | 2 +- tests/PhpGenerator/Factory.phpt | 6 +++--- tests/PhpGenerator/Method.returnTypes.phpt | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index c533e4f7..3ae08ef2 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -159,7 +159,7 @@ public static function createObject(string $class, array $props): object public static function validateType(?string $type, bool &$nullable): ?string { if ($type === '' || $type === null) { - return null; + return null; } if (!preg_match('#(?: \?[\w\\\\]+| diff --git a/tests/PhpGenerator/ClassType.from.phpt b/tests/PhpGenerator/ClassType.from.phpt index 7d170a0e..41fd1b57 100644 --- a/tests/PhpGenerator/ClassType.from.phpt +++ b/tests/PhpGenerator/ClassType.from.phpt @@ -21,7 +21,7 @@ $res[] = ClassType::from(Abc\Class1::class); $res[] = ClassType::from(new Abc\Class2); $obj = new Abc\Class3; $obj->prop2 = 1; -$res[] = (new Factory)->fromClassReflection(new \ReflectionObject($obj)); +$res[] = (new Factory)->fromClassReflection(new ReflectionObject($obj)); $res[] = ClassType::from(Abc\Class4::class); $res[] = ClassType::from(Abc\Class5::class); $res[] = ClassType::from(Abc\Class6::class); diff --git a/tests/PhpGenerator/Factory.phpt b/tests/PhpGenerator/Factory.phpt index 49643bbf..d1690398 100644 --- a/tests/PhpGenerator/Factory.phpt +++ b/tests/PhpGenerator/Factory.phpt @@ -26,17 +26,17 @@ Assert::type(Nette\PhpGenerator\ClassType::class, $res); Assert::null($res->getName()); -$res = $factory->fromMethodReflection(new \ReflectionMethod(ReflectionClass::class, 'getName')); +$res = $factory->fromMethodReflection(new ReflectionMethod(ReflectionClass::class, 'getName')); Assert::type(Nette\PhpGenerator\Method::class, $res); Assert::same('getName', $res->getName()); -$res = $factory->fromFunctionReflection(new \ReflectionFunction('trim')); +$res = $factory->fromFunctionReflection(new ReflectionFunction('trim')); Assert::type(Nette\PhpGenerator\GlobalFunction::class, $res); Assert::same('trim', $res->getName()); -$res = $factory->fromFunctionReflection(new \ReflectionFunction(function () {})); +$res = $factory->fromFunctionReflection(new ReflectionFunction(function () {})); Assert::type(Nette\PhpGenerator\Closure::class, $res); diff --git a/tests/PhpGenerator/Method.returnTypes.phpt b/tests/PhpGenerator/Method.returnTypes.phpt index 75a8a75e..68527385 100644 --- a/tests/PhpGenerator/Method.returnTypes.phpt +++ b/tests/PhpGenerator/Method.returnTypes.phpt @@ -25,7 +25,7 @@ namespace interface A { - public function testClass(): \A\Foo; + public function testClass(): A\Foo; public function testScalar(): string; } From 2072d9b558b7769320b6189dd93e620563b6027a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Dec 2021 03:38:16 +0100 Subject: [PATCH 085/266] cs whitespace --- src/PhpGenerator/Attribute.php | 1 + src/PhpGenerator/ClassType.php | 17 +++++++++++++++++ src/PhpGenerator/Closure.php | 1 + src/PhpGenerator/Dumper.php | 5 +++++ src/PhpGenerator/Extractor.php | 21 +++++++++++++++++++-- src/PhpGenerator/Factory.php | 19 +++++++++++++++++++ src/PhpGenerator/GlobalFunction.php | 1 + src/PhpGenerator/Helpers.php | 3 +++ src/PhpGenerator/Method.php | 2 ++ src/PhpGenerator/PhpFile.php | 4 ++++ src/PhpGenerator/PhpNamespace.php | 11 ++++++++++- src/PhpGenerator/Printer.php | 10 ++++++++++ src/PhpGenerator/TraitUse.php | 1 + src/PhpGenerator/Traits/FunctionLike.php | 2 ++ src/PhpGenerator/Traits/NameAware.php | 1 + src/PhpGenerator/Traits/VisibilityAware.php | 1 + 16 files changed, 97 insertions(+), 3 deletions(-) diff --git a/src/PhpGenerator/Attribute.php b/src/PhpGenerator/Attribute.php index 2c0f9a3b..f02d3da0 100644 --- a/src/PhpGenerator/Attribute.php +++ b/src/PhpGenerator/Attribute.php @@ -31,6 +31,7 @@ public function __construct(string $name, array $args) if (!Helpers::isNamespaceIdentifier($name)) { throw new Nette\InvalidArgumentException("Value '$name' is not valid attribute name."); } + $this->name = $name; $this->args = $args; } diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index fe092a2f..eab80215 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -138,6 +138,7 @@ public function __toString(): string if (PHP_VERSION_ID >= 70400) { throw $e; } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); return ''; } @@ -157,6 +158,7 @@ public function setName(?string $name): self if ($name !== null && (!Helpers::isIdentifier($name) || isset(Helpers::KEYWORDS[strtolower($name)]))) { throw new Nette\InvalidArgumentException("Value '$name' is not valid class name."); } + $this->name = $name; return $this; } @@ -222,6 +224,7 @@ public function setType(string $type): self if (!in_array($type, [self::TYPE_CLASS, self::TYPE_INTERFACE, self::TYPE_TRAIT, self::TYPE_ENUM], true)) { throw new Nette\InvalidArgumentException('Argument must be class|interface|trait|enum.'); } + $this->type = $type; return $this; } @@ -270,6 +273,7 @@ public function setExtends($names): self if (!is_string($names) && !is_array($names)) { throw new Nette\InvalidArgumentException('Argument must be string or string[].'); } + $this->validateNames((array) $names); $this->extends = $names; return $this; @@ -340,8 +344,10 @@ public function setTraits(array $traits): self if (!$trait instanceof TraitUse) { $trait = new TraitUse($trait); } + $this->traits[$trait->getName()] = $trait; } + return $this; } @@ -370,6 +376,7 @@ public function addTrait(string $name, $resolutions = []) if ($resolutions === true) { return $trait; } + array_map(function ($item) use ($trait) { $trait->addResolution($item); }, $resolutions); @@ -395,6 +402,7 @@ public function addMember($member): self if ($this->isInterface()) { $member->setBody(null); } + $this->methods[strtolower($member->getName())] = $member; } elseif ($member instanceof Property) { @@ -428,8 +436,10 @@ public function setConstants(array $consts): self if (!$const instanceof Constant) { $const = (new Constant($k))->setValue($const)->setPublic(); } + $this->consts[$const->getName()] = $const; } + return $this; } @@ -469,6 +479,7 @@ public function setCases(array $cases): self foreach ($cases as $case) { $this->cases[$case->getName()] = $case; } + return $this; } @@ -507,6 +518,7 @@ public function setProperties(array $props): self foreach ($props as $v) { $this->properties[$v->getName()] = $v; } + return $this; } @@ -523,6 +535,7 @@ public function getProperty(string $name): Property if (!isset($this->properties[$name])) { throw new Nette\InvalidArgumentException("Property '$name' not found."); } + return $this->properties[$name]; } @@ -566,6 +579,7 @@ public function setMethods(array $methods): self foreach ($methods as $m) { $this->methods[strtolower($m->getName())] = $m; } + return $this; } @@ -577,6 +591,7 @@ public function getMethods(): array foreach ($this->methods as $m) { $res[$m->getName()] = $m; } + return $res; } @@ -587,6 +602,7 @@ public function getMethod(string $name): Method if (!$m) { throw new Nette\InvalidArgumentException("Method '$name' not found."); } + return $m; } @@ -599,6 +615,7 @@ public function addMethod(string $name): Method } else { $method->setPublic(); } + return $this->methods[strtolower($name)] = $method; } diff --git a/src/PhpGenerator/Closure.php b/src/PhpGenerator/Closure.php index f504a738..b3ed8bf6 100644 --- a/src/PhpGenerator/Closure.php +++ b/src/PhpGenerator/Closure.php @@ -41,6 +41,7 @@ public function __toString(): string if (PHP_VERSION_ID >= 70400) { throw $e; } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); return ''; } diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 3597c1fe..ec6766af 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -155,6 +155,7 @@ private function dumpObject($var, array $parents, int $level): string ? '\Closure::fromCallable(' . $this->dump($inner) . ')' : implode('::', (array) $inner) . '(...)'; } + throw new Nette\InvalidArgumentException('Cannot dump closure.'); } @@ -227,6 +228,7 @@ public function format(string $statement, ...$args): string if (!is_array($arg)) { throw new Nette\InvalidArgumentException('Argument must be an array.'); } + $res .= $this->dumpArguments($arg, strlen($res) - strrpos($res, "\n"), $token === '...?:'); } else { // $ -> :: @@ -234,12 +236,15 @@ public function format(string $statement, ...$args): string if ($arg instanceof Literal || !Helpers::isIdentifier($arg)) { $arg = '{' . $this->dumpVar($arg) . '}'; } + $res .= substr($token, 0, -1) . $arg; } } + if ($args) { throw new Nette\InvalidArgumentException('Insufficient number of placeholders.'); } + return $res; } diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index e330b4d1..f46faa5c 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -34,6 +34,7 @@ public function __construct(string $code) if (!class_exists(ParserFactory::class)) { throw new Nette\NotSupportedException("PHP-Parser is required to load method bodies, install package 'nikic/php-parser' 4.7 or newer."); } + $this->printer = new PhpParser\PrettyPrinter\Standard; $this->parseCode($code); } @@ -44,6 +45,7 @@ private function parseCode(string $code): void if (substr($code, 0, 5) !== 'code = str_replace("\r\n", "\n", $code); $lexer = new PhpParser\Lexer\Emulative(['usedAttributes' => ['startFilePos', 'endFilePos', 'comments']]); $parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7, $lexer); @@ -71,6 +73,7 @@ public function extractMethodBodies(string $className): array $res[$methodNode->name->toString()] = $this->getReformattedContents($methodNode->stmts, 2); } } + return $res; } @@ -111,7 +114,6 @@ private function prepareReplacements(array $statements): array Helpers::tagName($node->toCodeString(), $of), ]; } - } elseif ($node instanceof Node\Scalar\String_ || $node instanceof Node\Scalar\EncapsedStringPart) { // multi-line strings => singleline $token = $this->getNodeContents($node); @@ -123,7 +125,6 @@ private function prepareReplacements(array $statements): array $quote . addcslashes($node->value, "\x00..\x1F") . $quote, ]; } - } elseif ($node instanceof Node\Scalar\Encapsed) { // HEREDOC => "string" if ($node->getAttribute('kind') === Node\Scalar\String_::KIND_HEREDOC) { @@ -153,6 +154,7 @@ private function performReplacements(string $s, array $replacements): string foreach ($replacements as [$start, $end, $replacement]) { $s = substr_replace($s, $replacement, $start, $end - $start + 1); } + return $s; } @@ -179,6 +181,7 @@ public function enterNode(Node $node) if (!$node->name) { return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; } + $class = $this->addClassToFile($phpFile, $node); } elseif ($node instanceof Node\Stmt\Interface_) { $class = $this->addInterfaceToFile($phpFile, $node); @@ -199,6 +202,7 @@ public function enterNode(Node $node) } elseif ($node instanceof Node\Stmt\EnumCase) { $this->addEnumCaseToClass($class, $node); } + if ($node instanceof Node\FunctionLike) { return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; } @@ -230,9 +234,11 @@ private function addClassToFile(PhpFile $phpFile, Node\Stmt\Class_ $node): Class if ($node->extends) { $class->setExtends($node->extends->toString()); } + foreach ($node->implements as $item) { $class->addImplement($item->toString()); } + $class->setFinal($node->isFinal()); $class->setAbstract($node->isAbstract()); $this->addCommentAndAttributes($class, $node); @@ -246,6 +252,7 @@ private function addInterfaceToFile(PhpFile $phpFile, Node\Stmt\Interface_ $node foreach ($node->extends as $item) { $class->addExtend($item->toString()); } + $this->addCommentAndAttributes($class, $node); return $class; } @@ -265,6 +272,7 @@ private function addEnumToFile(PhpFile $phpFile, Node\Stmt\Enum_ $node): ClassTy foreach ($node->implements as $item) { $class->addImplement($item->toString()); } + $this->addCommentAndAttributes($class, $node); return $class; } @@ -282,9 +290,11 @@ private function addTraitToClass(ClassType $class, Node\Stmt\TraitUse $node): vo foreach ($node->traits as $item) { $trait = $class->addTrait($item->toString(), true); } + foreach ($node->adaptations as $item) { $trait->addResolution(trim($this->toPhp($item), ';')); } + $this->addCommentAndAttributes($trait, $node); } @@ -299,10 +309,12 @@ private function addPropertyToClass(ClassType $class, Node\Stmt\Property $node): } elseif ($node->isProtected()) { $prop->setProtected(); } + $prop->setType($node->type ? $this->toPhp($node->type) : null); if ($item->default) { $prop->setValue(new Literal($this->getReformattedContents([$item->default], 1))); } + $prop->setReadOnly(method_exists($node, 'isReadonly') && $node->isReadonly()); $this->addCommentAndAttributes($prop, $node); } @@ -320,6 +332,7 @@ private function addMethodToClass(ClassType $class, Node\Stmt\ClassMethod $node) } elseif ($node->isProtected()) { $method->setProtected(); } + $this->setupFunction($method, $node); } @@ -334,6 +347,7 @@ private function addConstantToClass(ClassType $class, Node\Stmt\ClassConst $node } elseif ($node->isProtected()) { $const->setProtected(); } + $const->setFinal(method_exists($node, 'isFinal') && $node->isFinal()); $this->addCommentAndAttributes($const, $node); } @@ -366,6 +380,7 @@ private function addCommentAndAttributes($element, Node $node): void $args[] = $value; } } + $element->addAttribute($attribute->name->toString(), $args); } } @@ -387,8 +402,10 @@ private function setupFunction($function, Node\FunctionLike $node): void if ($item->default) { $param->setDefaultValue(new Literal($this->getReformattedContents([$item->default], 2))); } + $this->addCommentAndAttributes($param, $item); } + $this->addCommentAndAttributes($function, $node); if ($node->stmts) { $function->setBody($this->getReformattedContents($node->stmts, 2)); diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index bd1a0c50..176037c3 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -54,6 +54,7 @@ public function fromClassReflection( return !is_subclass_of($iface, $item); }); } + if ($from->isInterface()) { $class->setExtends($ifaces); } else { @@ -82,6 +83,7 @@ public function fromClassReflection( $props[] = $this->fromPropertyReflection($prop); } } + $class->setProperties($props); $methods = $resolutions = []; @@ -112,6 +114,7 @@ public function fromClassReflection( $resolutions[] = $realMethod->name . ' as' . $modifier . $alias; } } + $class->setMethods($methods); if (!$materializeTraits) { @@ -129,6 +132,7 @@ public function fromClassReflection( $consts[] = $this->fromConstantReflection($const); } } + $class->setConstants($consts); $class->setCases($cases); @@ -159,6 +163,7 @@ public function fromMethodReflection(\ReflectionMethod $from): Method ) { $method->setReturnType((string) $from->getReturnType()); } + return $method; } @@ -173,6 +178,7 @@ public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody if (!$from->isClosure()) { $function->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); } + $function->setAttributes(self::getAttributes($from)); if ($from->getReturnType() instanceof \ReflectionNamedType) { $function->setReturnType($from->getReturnType()->getName()); @@ -183,12 +189,15 @@ public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody ) { $function->setReturnType((string) $from->getReturnType()); } + if ($withBody) { if ($from->isClosure()) { throw new Nette\NotSupportedException('The $withBody parameter cannot be used for closures.'); } + $function->setBody($this->getExtractor($from)->extractFunctionBody($from->name)); } + return $function; } @@ -218,20 +227,24 @@ public function fromParameterReflection(\ReflectionParameter $from): Parameter ) { $param->setType((string) $from->getType()); } + if ($from->isDefaultValueAvailable()) { if ($from->isDefaultValueConstant()) { $parts = explode('::', $from->getDefaultValueConstantName()); if (count($parts) > 1) { $parts[0] = Helpers::tagName($parts[0]); } + $param->setDefaultValue(new Literal(implode('::', $parts))); } elseif (is_object($from->getDefaultValue())) { $param->setDefaultValue($this->fromObject($from->getDefaultValue())); } else { $param->setDefaultValue($from->getDefaultValue()); } + $param->setNullable($param->isNullable() && $param->getDefaultValue() !== null); } + $param->setAttributes(self::getAttributes($from)); return $param; } @@ -276,11 +289,13 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property ) { $prop->setType((string) $from->getType()); } + $prop->setInitialized($from->hasType() && array_key_exists($prop->getName(), $defaults)); $prop->setReadOnly(PHP_VERSION_ID >= 80100 ? $from->isReadOnly() : false); } else { $prop->setInitialized(false); } + $prop->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); $prop->setAttributes(self::getAttributes($from)); return $prop; @@ -299,6 +314,7 @@ public function fromClassCode(string $code): ClassType if (!$classes) { throw new Nette\InvalidStateException('The code does not contain any class.'); } + return reset($classes); } @@ -315,6 +331,7 @@ private function getAttributes($from): array if (PHP_VERSION_ID < 80000) { return []; } + return array_map(function ($attr) { $args = $attr->getArguments(); foreach ($args as &$arg) { @@ -322,6 +339,7 @@ private function getAttributes($from): array $arg = $this->fromObject($arg); } } + return new Attribute($attr->getName(), $args); }, $from->getAttributes()); } @@ -344,6 +362,7 @@ private function getExtractor($from): Extractor } elseif (!$file) { throw new Nette\InvalidStateException("Source code of $from->name not found."); } + return new Extractor(file_get_contents($file)); } } diff --git a/src/PhpGenerator/GlobalFunction.php b/src/PhpGenerator/GlobalFunction.php index 08faaf46..0245fe64 100644 --- a/src/PhpGenerator/GlobalFunction.php +++ b/src/PhpGenerator/GlobalFunction.php @@ -45,6 +45,7 @@ public function __toString(): string if (PHP_VERSION_ID >= 70400) { throw $e; } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); return ''; } diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 3ae08ef2..74c61b1d 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -161,16 +161,19 @@ public static function validateType(?string $type, bool &$nullable): ?string if ($type === '' || $type === null) { return null; } + if (!preg_match('#(?: \?[\w\\\\]+| [\w\\\\]+ (?: (&[\w\\\\]+)* | (\|[\w\\\\]+)* ) )()$#xAD', $type)) { throw new Nette\InvalidArgumentException("Value '$type' is not valid type."); } + if ($type[0] === '?') { $nullable = true; return substr($type, 1); } + return $type; } } diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index fd0a151d..a3abf503 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -56,6 +56,7 @@ public function __toString(): string if (PHP_VERSION_ID >= 70400) { throw $e; } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); return ''; } @@ -129,6 +130,7 @@ public function addPromotedParameter(string $name, $defaultValue = null): Promot if (func_num_args() > 1) { $param->setDefaultValue($defaultValue); } + return $this->parameters[$name] = $param; } diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index 31956ead..5741a778 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -86,6 +86,7 @@ public function addNamespace($namespace): PhpNamespace foreach ($this->namespaces as $namespace) { $namespace->setBracketedSyntax(count($this->namespaces) > 1 && isset($this->namespaces[''])); } + return $res; } @@ -115,6 +116,7 @@ public function getClasses(): array $classes[$n . $c] = $class; } } + return $classes; } @@ -129,6 +131,7 @@ public function getFunctions(): array $functions[$n . $f] = $function; } } + return $functions; } @@ -173,6 +176,7 @@ public function __toString(): string if (PHP_VERSION_ID >= 70400) { throw $e; } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); return ''; } diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 6348175d..b316b93b 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -55,6 +55,7 @@ public function __construct(string $name) if ($name !== '' && !Helpers::isNamespaceIdentifier($name)) { throw new Nette\InvalidArgumentException("Value '$name' is not valid name."); } + $this->name = $name; } @@ -117,7 +118,6 @@ public function addUse(string $name, string $alias = null, string $of = self::NA $lower = strtolower($alias); $counter++; } while ((isset($aliases[$lower]) && strcasecmp($aliases[$lower], $name) !== 0) || isset($used[$lower])); - } else { $lower = strtolower($alias); if (isset($aliases[$lower]) && strcasecmp($aliases[$lower], $name) !== 0) { @@ -199,6 +199,7 @@ public function simplifyName(string $name, string $of = self::NAME_NORMAL): stri if (isset(Helpers::KEYWORDS[strtolower($name)]) || $name === '') { return $name; } + $name = ltrim($name, '\\'); if ($of !== self::NAME_NORMAL) { @@ -207,6 +208,7 @@ public function simplifyName(string $name, string $of = self::NAME_NORMAL): stri return $alias; } } + return $this->simplifyName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name); } @@ -219,6 +221,7 @@ public function simplifyName(string $name, string $of = self::NAME_NORMAL): stri if ($relative && self::startsWith($relative . '\\', $alias . '\\')) { $relative = null; } + if (self::startsWith($name . '\\', $original . '\\')) { $short = $alias . substr($name, strlen($original)); if (!isset($shortest) || strlen($shortest) > strlen($short)) { @@ -242,10 +245,12 @@ public function add(ClassType $class): self if ($name === null) { throw new Nette\InvalidArgumentException('Class does not have a name.'); } + $lower = strtolower($name); if ($orig = array_change_key_case($this->aliases[self::NAME_NORMAL])[$lower] ?? null) { throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig."); } + $this->classes[$lower] = $class; return $this; } @@ -282,6 +287,7 @@ public function addFunction(string $name): GlobalFunction if ($orig = array_change_key_case($this->aliases[self::NAME_FUNCTION])[$lower] ?? null) { throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig."); } + return $this->functions[$lower] = new GlobalFunction($name); } @@ -293,6 +299,7 @@ public function getClasses(): array foreach ($this->classes as $class) { $res[$class->getName()] = $class; } + return $res; } @@ -304,6 +311,7 @@ public function getFunctions(): array foreach ($this->functions as $fn) { $res[$fn->getName()] = $fn; } + return $res; } @@ -322,6 +330,7 @@ public function __toString(): string if (PHP_VERSION_ID >= 70400) { throw $e; } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); return ''; } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 112f22ad..cc2040d2 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -76,6 +76,7 @@ public function printClosure(Closure $closure, PhpNamespace $namespace = null): foreach ($closure->getUses() as $param) { $uses[] = ($param->isReference() ? '&' : '') . '$' . $param->getName(); } + $useStr = strlen($tmp = implode(', ', $uses)) > $this->wrapLength && count($uses) > 1 ? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . "\n" : $tmp; @@ -99,6 +100,7 @@ public function printArrowFunction(Closure $closure, PhpNamespace $namespace = n throw new Nette\InvalidArgumentException('Arrow function cannot bind variables by-reference.'); } } + $body = Helpers::simplifyTaggedNames($closure->getBody(), $this->namespace); return self::printAttributes($closure->getAttributes()) @@ -165,6 +167,7 @@ public function printClass(ClassType $class, PhpNamespace $namespace = null): st . ($case->getValue() === null ? '' : ' = ' . $this->dump($case->getValue())) . ";\n"; } + $enumType = isset($case) && $case->getValue() !== null ? $this->returnTypeColon . Type::getType($case->getValue()) : ''; @@ -242,6 +245,7 @@ public function printNamespace(PhpNamespace $namespace): string foreach ($namespace->getClasses() as $class) { $items[] = $this->printClass($class, $namespace); } + foreach ($namespace->getFunctions() as $function) { $items[] = $this->printFunction($function, $namespace); } @@ -292,6 +296,7 @@ protected function printUses(PhpNamespace $namespace, string $of = PhpNamespace: ? "use $prefix$original;\n" : "use $prefix$original as $alias;\n"; } + return implode('', $uses); } @@ -339,14 +344,17 @@ protected function printType(?string $type, bool $nullable): string if ($type === null) { return ''; } + if ($this->namespace) { $type = $this->namespace->simplifyType($type); } + if ($nullable && strcasecmp($type, 'mixed')) { $type = strpos($type, '|') === false ? '?' . $type : $type . '|null'; } + return $type; } @@ -367,6 +375,7 @@ private function printAttributes(array $attrs, bool $inline = false): string if (!$attrs) { return ''; } + $this->dumper->indentation = $this->indentation; $items = []; foreach ($attrs as $attr) { @@ -374,6 +383,7 @@ private function printAttributes(array $attrs, bool $inline = false): string $args = Helpers::simplifyTaggedNames($args, $this->namespace); $items[] = $this->printType($attr->getName(), false) . ($args ? "($args)" : ''); } + return $inline ? '#[' . implode(', ', $items) . '] ' : '#[' . implode("]\n#[", $items) . "]\n"; diff --git a/src/PhpGenerator/TraitUse.php b/src/PhpGenerator/TraitUse.php index 4b8b457a..12514014 100644 --- a/src/PhpGenerator/TraitUse.php +++ b/src/PhpGenerator/TraitUse.php @@ -30,6 +30,7 @@ public function __construct(string $name) if (!Nette\PhpGenerator\Helpers::isNamespaceIdentifier($name, true)) { throw new Nette\InvalidArgumentException("Value '$name' is not valid trait name."); } + $this->name = $name; } diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index 151e73e2..4c64a749 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -74,6 +74,7 @@ public function setParameters(array $val): self foreach ($val as $v) { $this->parameters[$v->getName()] = $v; } + return $this; } @@ -94,6 +95,7 @@ public function addParameter(string $name, $defaultValue = null): Parameter if (func_num_args() > 1) { $param->setDefaultValue($defaultValue); } + return $this->parameters[$name] = $param; } diff --git a/src/PhpGenerator/Traits/NameAware.php b/src/PhpGenerator/Traits/NameAware.php index 157aa0f8..bb7e356a 100644 --- a/src/PhpGenerator/Traits/NameAware.php +++ b/src/PhpGenerator/Traits/NameAware.php @@ -26,6 +26,7 @@ public function __construct(string $name) if (!Nette\PhpGenerator\Helpers::isIdentifier($name)) { throw new Nette\InvalidArgumentException("Value '$name' is not valid name."); } + $this->name = $name; } diff --git a/src/PhpGenerator/Traits/VisibilityAware.php b/src/PhpGenerator/Traits/VisibilityAware.php index 3dd862c7..113953a4 100644 --- a/src/PhpGenerator/Traits/VisibilityAware.php +++ b/src/PhpGenerator/Traits/VisibilityAware.php @@ -31,6 +31,7 @@ public function setVisibility(?string $val): self if (!in_array($val, [ClassType::VISIBILITY_PUBLIC, ClassType::VISIBILITY_PROTECTED, ClassType::VISIBILITY_PRIVATE, null], true)) { throw new Nette\InvalidArgumentException('Argument must be public|protected|private.'); } + $this->visibility = $val; return $this; } From 2d57a84b1fa2d9d95a4efa2613f3230987c7affc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Dec 2021 18:17:13 +0100 Subject: [PATCH 086/266] cs nullable typehints --- src/PhpGenerator/ClassType.php | 2 +- src/PhpGenerator/Literal.php | 2 +- src/PhpGenerator/Method.php | 2 +- src/PhpGenerator/PhpFile.php | 2 +- src/PhpGenerator/PhpNamespace.php | 6 +++--- src/PhpGenerator/Printer.php | 10 +++++----- src/PhpGenerator/Traits/FunctionLike.php | 6 +++--- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index eab80215..ec2f38b2 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -123,7 +123,7 @@ public static function fromCode(string $code): self } - public function __construct(string $name = null, PhpNamespace $namespace = null) + public function __construct(?string $name = null, ?PhpNamespace $namespace = null) { $this->setName($name); $this->namespace = $namespace; diff --git a/src/PhpGenerator/Literal.php b/src/PhpGenerator/Literal.php index fb180b4f..386b66aa 100644 --- a/src/PhpGenerator/Literal.php +++ b/src/PhpGenerator/Literal.php @@ -22,7 +22,7 @@ class Literal private $args; - public function __construct(string $value, array $args = null) + public function __construct(string $value, ?array $args = null) { $this->value = $value; $this->args = $args; diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index a3abf503..c8b1d779 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -64,7 +64,7 @@ public function __toString(): string /** @return static */ - public function setBody(?string $code, array $args = null): self + public function setBody(?string $code, ?array $args = null): self { $this->body = $args === null || $code === null ? $code diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index 5741a778..c06ad451 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -137,7 +137,7 @@ public function getFunctions(): array /** @return static */ - public function addUse(string $name, string $alias = null, string $of = PhpNamespace::NAME_NORMAL): self + public function addUse(string $name, ?string $alias = null, string $of = PhpNamespace::NAME_NORMAL): self { $this->addNamespace('')->addUse($name, $alias, $of); return $this; diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index b316b93b..f2c8ee25 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -94,7 +94,7 @@ public function getBracketedSyntax(): bool * @throws InvalidStateException * @return static */ - public function addUse(string $name, string $alias = null, string $of = self::NAME_NORMAL): self + public function addUse(string $name, ?string $alias = null, string $of = self::NAME_NORMAL): self { if ( !Helpers::isNamespaceIdentifier($name, true) @@ -135,14 +135,14 @@ public function addUse(string $name, string $alias = null, string $of = self::NA /** @return static */ - public function addUseFunction(string $name, string $alias = null): self + public function addUseFunction(string $name, ?string $alias = null): self { return $this->addUse($name, $alias, self::NAME_FUNCTION); } /** @return static */ - public function addUseConstant(string $name, string $alias = null): self + public function addUseConstant(string $name, ?string $alias = null): self { return $this->addUse($name, $alias, self::NAME_CONSTANT); } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index cc2040d2..668d1ea3 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -51,7 +51,7 @@ public function __construct() } - public function printFunction(GlobalFunction $function, PhpNamespace $namespace = null): string + public function printFunction(GlobalFunction $function, ?PhpNamespace $namespace = null): string { $this->namespace = $this->resolveTypes ? $namespace : null; $line = 'function ' @@ -69,7 +69,7 @@ public function printFunction(GlobalFunction $function, PhpNamespace $namespace } - public function printClosure(Closure $closure, PhpNamespace $namespace = null): string + public function printClosure(Closure $closure, ?PhpNamespace $namespace = null): string { $this->namespace = $this->resolveTypes ? $namespace : null; $uses = []; @@ -92,7 +92,7 @@ public function printClosure(Closure $closure, PhpNamespace $namespace = null): } - public function printArrowFunction(Closure $closure, PhpNamespace $namespace = null): string + public function printArrowFunction(Closure $closure, ?PhpNamespace $namespace = null): string { $this->namespace = $this->resolveTypes ? $namespace : null; foreach ($closure->getUses() as $use) { @@ -112,7 +112,7 @@ public function printArrowFunction(Closure $closure, PhpNamespace $namespace = n } - public function printMethod(Method $method, PhpNamespace $namespace = null): string + public function printMethod(Method $method, ?PhpNamespace $namespace = null): string { $this->namespace = $this->resolveTypes ? $namespace : null; $method->validate(); @@ -141,7 +141,7 @@ public function printMethod(Method $method, PhpNamespace $namespace = null): str } - public function printClass(ClassType $class, PhpNamespace $namespace = null): string + public function printClass(ClassType $class, ?PhpNamespace $namespace = null): string { $this->namespace = $this->resolveTypes ? $namespace : null; $class->validate(); diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index 4c64a749..7202df61 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -40,7 +40,7 @@ trait FunctionLike /** @return static */ - public function setBody(string $code, array $args = null): self + public function setBody(string $code, ?array $args = null): self { $this->body = $args === null ? $code @@ -56,7 +56,7 @@ public function getBody(): string /** @return static */ - public function addBody(string $code, array $args = null): self + public function addBody(string $code, ?array $args = null): self { $this->body .= ($args === null ? $code : (new Dumper)->format($code, ...$args)) . "\n"; return $this; @@ -180,7 +180,7 @@ public function getReturnNullable(): bool /** @deprecated */ - public function setNamespace(Nette\PhpGenerator\PhpNamespace $val = null): self + public function setNamespace(?Nette\PhpGenerator\PhpNamespace $val = null): self { trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED); return $this; From 2a09a7aa5b05bd513b30735a796739f2ff85c918 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 24 Jan 2022 13:10:29 +0100 Subject: [PATCH 087/266] PhpNamespace: added removeClass() & removeFunction() --- src/PhpGenerator/PhpNamespace.php | 14 ++++++++++++++ tests/PhpGenerator/PhpNamespace.phpt | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index f2c8ee25..f0171019 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -281,6 +281,13 @@ public function addEnum(string $name): ClassType } + public function removeClass(string $name): self + { + unset($this->classes[strtolower($name)]); + return $this; + } + + public function addFunction(string $name): GlobalFunction { $lower = strtolower($name); @@ -292,6 +299,13 @@ public function addFunction(string $name): GlobalFunction } + public function removeFunction(string $name): self + { + unset($this->functions[strtolower($name)]); + return $this; + } + + /** @return ClassType[] */ public function getClasses(): array { diff --git a/tests/PhpGenerator/PhpNamespace.phpt b/tests/PhpGenerator/PhpNamespace.phpt index cabba8bf..2fb94a27 100644 --- a/tests/PhpGenerator/PhpNamespace.phpt +++ b/tests/PhpGenerator/PhpNamespace.phpt @@ -22,3 +22,11 @@ Assert::same($namespace, $interfaceB->getNamespace()); Assert::count(2, $namespace->getClasses()); Assert::type(Nette\PhpGenerator\ClassType::class, $namespace->getClasses()['A']); +$namespace->removeClass('a'); +Assert::count(1, $namespace->getClasses()); + +$function = $namespace->addFunction('foo'); +Assert::count(1, $namespace->getFunctions()); +Assert::same($function, $namespace->getFunctions()['foo']); +$namespace->removeFunction('FOO'); +Assert::count(0, $namespace->getFunctions()); From ad7d0a77f58260001bd263605eb2b9fbaf1d61a7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 20 Feb 2022 19:05:39 +0100 Subject: [PATCH 088/266] Extractor: preserves the first comment in the file [Closes #100] --- src/PhpGenerator/Extractor.php | 5 +++++ tests/PhpGenerator/expected/Extractor.classes.expect | 4 ++++ tests/PhpGenerator/fixtures/classes.php | 8 ++++++++ 3 files changed, 17 insertions(+) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index f46faa5c..5653e81a 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -208,6 +208,10 @@ public function enterNode(Node $node) } }; + if ($this->statements) { + $this->addCommentAndAttributes($phpFile, $this->statements[0]); + } + $traverser = new PhpParser\NodeTraverser; $traverser->addVisitor($visitor); $traverser->traverse($this->statements); @@ -367,6 +371,7 @@ private function addCommentAndAttributes($element, Node $node): void $comment = $node->getDocComment()->getReformattedText(); $comment = Helpers::unformatDocComment($comment); $element->setComment($comment); + $node->setDocComment(new PhpParser\Comment\Doc('')); } foreach ($node->attrGroups ?? [] as $group) { diff --git a/tests/PhpGenerator/expected/Extractor.classes.expect b/tests/PhpGenerator/expected/Extractor.classes.expect index bd418d73..a4485052 100644 --- a/tests/PhpGenerator/expected/Extractor.classes.expect +++ b/tests/PhpGenerator/expected/Extractor.classes.expect @@ -1,5 +1,9 @@ Date: Mon, 1 Mar 2021 15:30:01 +0100 Subject: [PATCH 089/266] opened 4.0-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 388d21cf..28e89b29 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.6-dev" + "dev-master": "4.0-dev" } } } From 1ebeb8a2082a3da9739fb83e016f54f704ab9cc6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 24 Aug 2021 16:19:02 +0200 Subject: [PATCH 090/266] requires PHP 8.0 --- .github/workflows/coding-style.yml | 2 +- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 6 +++--- composer.json | 2 +- readme.md | 1 + 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index 45c4ac66..849bef94 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v1 with: - php-version: 7.2 + php-version: 8.0 coverage: none - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 36203715..94b3ec65 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 266902db..82b6f186 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.2', '7.3', '7.4', '8.0', '8.1'] + php: ['8.0', '8.1'] fail-fast: false @@ -35,7 +35,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.2 + php-version: 8.0 coverage: none - run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/composer.json b/composer.json index 28e89b29..2f7343bc 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=7.2 <8.2", + "php": ">=8.0 <8.2", "nette/utils": "^3.1.2" }, "require-dev": { diff --git a/readme.md b/readme.md index d3cfe485..04387ff1 100644 --- a/readme.md +++ b/readme.md @@ -33,6 +33,7 @@ Installation composer require nette/php-generator ``` +- PhpGenerator 4.0 is compatible with PHP 8.0 to 8.1 - PhpGenerator 3.6 is compatible with PHP 7.2 to 8.1 - PhpGenerator 3.2 – 3.5 is compatible with PHP 7.1 to 8.0 - PhpGenerator 3.1 is compatible with PHP 7.1 to 7.3 From 0a2ef502926f880fa748c2bbe8fabe75fed80c80 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 22 Sep 2021 11:51:26 +0200 Subject: [PATCH 091/266] composer: updated dependencies --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 2f7343bc..3c5dbf23 100644 --- a/composer.json +++ b/composer.json @@ -16,13 +16,13 @@ ], "require": { "php": ">=8.0 <8.2", - "nette/utils": "^3.1.2" + "nette/utils": "^3.2 || ^4.0" }, "require-dev": { "nette/tester": "^2.4", "nikic/php-parser": "^4.13", "tracy/tracy": "^2.8", - "phpstan/phpstan": "^0.12" + "phpstan/phpstan": "^1.0" }, "suggest": { "nikic/php-parser": "to use ClassType::withBodiesFrom() & GlobalFunction::withBodyFrom()" From fe54e5121b145203abe2b465bcda3a56793d77b3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 15 Sep 2021 19:21:35 +0200 Subject: [PATCH 092/266] removed support for PHP 7 --- src/PhpGenerator/ClassType.php | 11 +--------- src/PhpGenerator/Closure.php | 11 +--------- src/PhpGenerator/Factory.php | 32 +++++++++++------------------ src/PhpGenerator/GlobalFunction.php | 11 +--------- src/PhpGenerator/Method.php | 11 +--------- src/PhpGenerator/PhpFile.php | 11 +--------- src/PhpGenerator/PhpNamespace.php | 11 +--------- 7 files changed, 18 insertions(+), 80 deletions(-) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index ec2f38b2..c4888b2a 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -132,16 +132,7 @@ public function __construct(?string $name = null, ?PhpNamespace $namespace = nul public function __toString(): string { - try { - return (new Printer)->printClass($this, $this->namespace); - } catch (\Throwable $e) { - if (PHP_VERSION_ID >= 70400) { - throw $e; - } - - trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); - return ''; - } + return (new Printer)->printClass($this, $this->namespace); } diff --git a/src/PhpGenerator/Closure.php b/src/PhpGenerator/Closure.php index b3ed8bf6..636ed271 100644 --- a/src/PhpGenerator/Closure.php +++ b/src/PhpGenerator/Closure.php @@ -35,16 +35,7 @@ public static function from(\Closure $closure): self public function __toString(): string { - try { - return (new Printer)->printClosure($this); - } catch (\Throwable $e) { - if (PHP_VERSION_ID >= 70400) { - throw $e; - } - - trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); - return ''; - } + return (new Printer)->printClosure($this); } diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 176037c3..a15785fa 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -77,7 +77,7 @@ public function fromClassReflection( if ($prop->isDefault() && $declaringClass->name === $from->name - && (PHP_VERSION_ID < 80000 || !$prop->isPromoted()) + && !$prop->isPromoted() && !$class->isEnum() ) { $props[] = $this->fromPropertyReflection($prop); @@ -214,7 +214,7 @@ public function fromCallable(callable $from) public function fromParameterReflection(\ReflectionParameter $from): Parameter { - $param = PHP_VERSION_ID >= 80000 && $from->isPromoted() + $param = $from->isPromoted() ? new PromotedParameter($from->name) : new Parameter($from->name); $param->setReference($from->isPassedByReference()); @@ -279,23 +279,18 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property $prop->setValue($defaults[$prop->getName()] ?? null); $prop->setStatic($from->isStatic()); $prop->setVisibility($this->getVisibility($from)); - if (PHP_VERSION_ID >= 70400) { - if ($from->getType() instanceof \ReflectionNamedType) { - $prop->setType($from->getType()->getName()); - $prop->setNullable($from->getType()->allowsNull()); - } elseif ( - $from->getType() instanceof \ReflectionUnionType - || $from->getType() instanceof \ReflectionIntersectionType - ) { - $prop->setType((string) $from->getType()); - } - - $prop->setInitialized($from->hasType() && array_key_exists($prop->getName(), $defaults)); - $prop->setReadOnly(PHP_VERSION_ID >= 80100 ? $from->isReadOnly() : false); - } else { - $prop->setInitialized(false); + if ($from->getType() instanceof \ReflectionNamedType) { + $prop->setType($from->getType()->getName()); + $prop->setNullable($from->getType()->allowsNull()); + } elseif ( + $from->getType() instanceof \ReflectionUnionType + || $from->getType() instanceof \ReflectionIntersectionType + ) { + $prop->setType((string) $from->getType()); } + $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)); return $prop; @@ -328,9 +323,6 @@ public function fromCode(string $code): PhpFile private function getAttributes($from): array { - if (PHP_VERSION_ID < 80000) { - return []; - } return array_map(function ($attr) { $args = $attr->getArguments(); diff --git a/src/PhpGenerator/GlobalFunction.php b/src/PhpGenerator/GlobalFunction.php index 0245fe64..7e40f363 100644 --- a/src/PhpGenerator/GlobalFunction.php +++ b/src/PhpGenerator/GlobalFunction.php @@ -39,15 +39,6 @@ public static function withBodyFrom(string $function): self public function __toString(): string { - try { - return (new Printer)->printFunction($this); - } catch (\Throwable $e) { - if (PHP_VERSION_ID >= 70400) { - throw $e; - } - - trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); - return ''; - } + return (new Printer)->printFunction($this); } } diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index c8b1d779..8d1c7187 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -50,16 +50,7 @@ public static function from($method): self public function __toString(): string { - try { - return (new Printer)->printMethod($this); - } catch (\Throwable $e) { - if (PHP_VERSION_ID >= 70400) { - throw $e; - } - - trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); - return ''; - } + return (new Printer)->printMethod($this); } diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index c06ad451..b51d7001 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -170,15 +170,6 @@ public function getStrictTypes(): bool public function __toString(): string { - try { - return (new Printer)->printFile($this); - } catch (\Throwable $e) { - if (PHP_VERSION_ID >= 70400) { - throw $e; - } - - trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); - return ''; - } + return (new Printer)->printFile($this); } } diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index f0171019..0b73d1b2 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -338,15 +338,6 @@ private static function startsWith(string $a, string $b): bool public function __toString(): string { - try { - return (new Printer)->printNamespace($this); - } catch (\Throwable $e) { - if (PHP_VERSION_ID >= 70400) { - throw $e; - } - - trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); - return ''; - } + return (new Printer)->printNamespace($this); } } From 059a93b935aa0bea22ab09fc1a515a5c4651952a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 22:14:13 +0100 Subject: [PATCH 093/266] Method: body is always string, even for interfaces (BC break) --- src/PhpGenerator/ClassType.php | 8 +------- src/PhpGenerator/Factory.php | 1 - src/PhpGenerator/Method.php | 19 ------------------- src/PhpGenerator/Printer.php | 8 ++++---- tests/PhpGenerator/ClassType.addMember.phpt | 2 -- 5 files changed, 5 insertions(+), 33 deletions(-) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index c4888b2a..e4f12c27 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -390,10 +390,6 @@ public function removeTrait(string $name): self public function addMember($member): self { if ($member instanceof Method) { - if ($this->isInterface()) { - $member->setBody(null); - } - $this->methods[strtolower($member->getName())] = $member; } elseif ($member instanceof Property) { @@ -601,9 +597,7 @@ public function getMethod(string $name): Method public function addMethod(string $name): Method { $method = new Method($name); - if ($this->isInterface()) { - $method->setBody(null); - } else { + if (!$this->isInterface()) { $method->setPublic(); } diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index a15785fa..0a2717fe 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -149,7 +149,6 @@ public function fromMethodReflection(\ReflectionMethod $from): Method $method->setVisibility($isInterface ? null : $this->getVisibility($from)); $method->setFinal($from->isFinal()); $method->setAbstract($from->isAbstract() && !$isInterface); - $method->setBody($from->isAbstract() ? null : ''); $method->setReturnReference($from->returnsReference()); $method->setVariadic($from->isVariadic()); $method->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index 8d1c7187..0f57578d 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -26,9 +26,6 @@ final class Method use Traits\CommentAware; use Traits\AttributeAware; - /** @var string|null */ - private $body = ''; - /** @var bool */ private $static = false; @@ -54,22 +51,6 @@ public function __toString(): string } - /** @return static */ - public function setBody(?string $code, ?array $args = null): self - { - $this->body = $args === null || $code === null - ? $code - : (new Dumper)->format($code, ...$args); - return $this; - } - - - public function getBody(): ?string - { - return $this->body; - } - - /** @return static */ public function setStatic(bool $state = true): self { diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 668d1ea3..3ff5fc24 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -112,11 +112,11 @@ public function printArrowFunction(Closure $closure, ?PhpNamespace $namespace = } - public function printMethod(Method $method, ?PhpNamespace $namespace = null): string + public function printMethod(Method $method, ?PhpNamespace $namespace = null, bool $isInterface = false): string { $this->namespace = $this->resolveTypes ? $namespace : null; $method->validate(); - $line = ($method->isAbstract() ? 'abstract ' : '') + $line = ($method->isAbstract() && !$isInterface ? 'abstract ' : '') . ($method->isFinal() ? 'final ' : '') . ($method->getVisibility() ? $method->getVisibility() . ' ' : '') . ($method->isStatic() ? 'static ' : '') @@ -132,7 +132,7 @@ public function printMethod(Method $method, ?PhpNamespace $namespace = null): st . $line . $params . $returnType - . ($method->isAbstract() || $method->getBody() === null + . ($method->isAbstract() || $isInterface ? ";\n" : (strpos($params, "\n") === false ? "\n" : ' ') . "{\n" @@ -206,7 +206,7 @@ public function printClass(ClassType $class, ?PhpNamespace $namespace = null): s $methods = []; foreach ($class->getMethods() as $method) { - $methods[] = $this->printMethod($method, $namespace); + $methods[] = $this->printMethod($method, $namespace, $class->isInterface()); } $members = array_filter([ diff --git a/tests/PhpGenerator/ClassType.addMember.phpt b/tests/PhpGenerator/ClassType.addMember.phpt index 0486303c..6a1ba458 100644 --- a/tests/PhpGenerator/ClassType.addMember.phpt +++ b/tests/PhpGenerator/ClassType.addMember.phpt @@ -32,5 +32,3 @@ Assert::same('', $method->getBody()); $class = (new ClassType('Example')) ->setType('interface') ->addMember($method = new Nette\PhpGenerator\Method('getHandle')); - -Assert::null($method->getBody()); From 49268f6c035d9509e8f3b9fef0ff2ab0e272e68b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 3 Oct 2021 00:01:36 +0200 Subject: [PATCH 094/266] coding style --- src/PhpGenerator/ClassType.php | 37 +++------ src/PhpGenerator/Dumper.php | 27 +++--- src/PhpGenerator/Extractor.php | 83 ++++++++----------- src/PhpGenerator/Factory.php | 16 ++-- src/PhpGenerator/GlobalFunction.php | 2 +- src/PhpGenerator/PhpFile.php | 2 +- src/PhpGenerator/PhpNamespace.php | 8 +- src/PhpGenerator/Printer.php | 12 +-- src/PhpGenerator/Type.php | 2 +- tests/PhpGenerator/ClassType.from.trait.phpt | 16 +--- tests/PhpGenerator/ClassType.fromCode.phpt | 19 ++--- tests/PhpGenerator/Closure.attributes.80.phpt | 2 +- tests/PhpGenerator/Closure.long.phpt | 2 +- tests/PhpGenerator/Closure.phpt | 10 +-- tests/PhpGenerator/Dumper.dump().indent.phpt | 2 +- tests/PhpGenerator/Dumper.dump().phpt | 20 ++--- tests/PhpGenerator/Dumper.dump().wrap.phpt | 6 +- tests/PhpGenerator/Dumper.format().phpt | 2 +- tests/PhpGenerator/Extractor.extractAll.phpt | 73 ++++++++-------- .../Extractor.getFunctionBody.phpt | 2 +- tests/PhpGenerator/Factory.fromClassCode.phpt | 19 ++--- .../GlobalFunction.attributes.80.phpt | 2 +- tests/PhpGenerator/GlobalFunction.phpt | 25 +++--- tests/PhpGenerator/Method.longParams.phpt | 2 +- tests/PhpGenerator/Method.returnTypes.phpt | 2 +- .../PhpGenerator/Method.scalarParameters.phpt | 2 +- tests/PhpGenerator/Method.variadics.phpt | 10 +-- 27 files changed, 178 insertions(+), 227 deletions(-) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index e4f12c27..40ef58b1 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -112,7 +112,7 @@ public static function from($class, bool $withBodies = false, bool $materializeT public static function withBodiesFrom($class): self { return (new Factory) - ->fromClassReflection(new \ReflectionClass($class), true); + ->fromClassReflection(new \ReflectionClass($class), withBodies: true); } @@ -368,9 +368,7 @@ public function addTrait(string $name, $resolutions = []) return $trait; } - array_map(function ($item) use ($trait) { - $trait->addResolution($item); - }, $resolutions); + array_map(fn($item) => $trait->addResolution($item), $resolutions); return $this; } @@ -389,25 +387,14 @@ public function removeTrait(string $name): self */ public function addMember($member): self { - if ($member instanceof Method) { - $this->methods[strtolower($member->getName())] = $member; - - } elseif ($member instanceof Property) { - $this->properties[$member->getName()] = $member; - - } elseif ($member instanceof Constant) { - $this->consts[$member->getName()] = $member; - - } elseif ($member instanceof EnumCase) { - $this->cases[$member->getName()] = $member; - - } elseif ($member instanceof TraitUse) { - $this->traits[$member->getName()] = $member; - - } else { - throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant|EnumCase|TraitUse.'); - } - + match (true) { + $member instanceof Method => $this->methods[strtolower($member->getName())] = $member, + $member instanceof Property => $this->properties[$member->getName()] = $member, + $member instanceof Constant => $this->consts[$member->getName()] = $member, + $member instanceof EnumCase => $this->cases[$member->getName()] = $member, + $member instanceof TraitUse => $this->traits[$member->getName()] = $member, + default => throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant|EnumCase|TraitUse.'), + }; return $this; } @@ -637,7 +624,7 @@ public function validate(): void private function validateNames(array $names): void { foreach ($names as $name) { - if (!Helpers::isNamespaceIdentifier($name, true)) { + if (!Helpers::isNamespaceIdentifier($name, allowLeadingSlash: true)) { throw new Nette\InvalidArgumentException("Value '$name' is not valid class name."); } } @@ -646,7 +633,7 @@ private function validateNames(array $names): void public function __clone() { - $clone = function ($item) { return clone $item; }; + $clone = fn($item) => clone $item; $this->cases = array_map($clone, $this->cases); $this->consts = array_map($clone, $this->consts); $this->properties = array_map($clone, $this->properties); diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index ec6766af..5f3fbfa2 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -77,12 +77,10 @@ private function dumpString(string $s): string $utf8 = preg_match('##u', $s); $escaped = preg_replace_callback( $utf8 ? '#[\p{C}\\\\]#u' : '#[\x00-\x1F\x7F-\xFF\\\\]#', - function ($m) use ($special) { - return $special[$m[0]] ?? (strlen($m[0]) === 1 + fn($m) => $special[$m[0]] ?? (strlen($m[0]) === 1 ? '\x' . str_pad(strtoupper(dechex(ord($m[0]))), 2, '0', STR_PAD_LEFT) . '' - : '\u{' . strtoupper(ltrim(dechex(self::utf8Ord($m[0])), '0')) . '}'); - }, - $s + : '\u{' . strtoupper(ltrim(dechex(self::utf8Ord($m[0])), '0')) . '}'), + $s, ); return $s === str_replace('\\\\', '\\', $escaped) ? "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|$)#D', '\\\\$0', $s) . "'" @@ -93,15 +91,12 @@ function ($m) use ($special) { private static function utf8Ord(string $c): int { $ord0 = ord($c[0]); - if ($ord0 < 0x80) { - return $ord0; - } elseif ($ord0 < 0xE0) { - return ($ord0 << 6) + ord($c[1]) - 0x3080; - } elseif ($ord0 < 0xF0) { - return ($ord0 << 12) + (ord($c[1]) << 6) + ord($c[2]) - 0xE2080; - } else { - return ($ord0 << 18) + (ord($c[1]) << 12) + (ord($c[2]) << 6) + ord($c[3]) - 0x3C82080; - } + return match (true) { + $ord0 < 0x80 => $ord0, + $ord0 < 0xE0 => ($ord0 << 6) + ord($c[1]) - 0x3080, + $ord0 < 0xF0 => ($ord0 << 12) + (ord($c[1]) << 6) + ord($c[2]) - 0xE2080, + default => ($ord0 << 18) + (ord($c[1]) << 12) + (ord($c[2]) << 6) + ord($c[3]) - 0x3C82080, + }; } @@ -146,7 +141,7 @@ private function dumpObject($var, array $parents, int $level): string return 'unserialize(' . $this->dumpString(serialize($var)) . ')'; } elseif ($var instanceof \UnitEnum) { - return '\\' . get_class($var) . '::' . $var->name; + return '\\' . $var::class . '::' . $var->name; } elseif ($var instanceof \Closure) { $inner = Nette\Utils\Callback::unwrap($var); @@ -159,7 +154,7 @@ private function dumpObject($var, array $parents, int $level): string throw new Nette\InvalidArgumentException('Cannot dump closure.'); } - $class = get_class($var); + $class = $var::class; if ((new \ReflectionObject($var))->isAnonymous()) { throw new Nette\InvalidArgumentException('Cannot dump anonymous class.'); diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 5653e81a..347d7570 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -61,10 +61,11 @@ private function parseCode(string $code): void public function extractMethodBodies(string $className): array { $nodeFinder = new NodeFinder; - $classNode = $nodeFinder->findFirst($this->statements, function (Node $node) use ($className) { - return ($node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Trait_) - && $node->namespacedName->toString() === $className; - }); + $classNode = $nodeFinder->findFirst( + $this->statements, + fn(Node $node) => ($node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Trait_) + && $node->namespacedName->toString() === $className, + ); $res = []; foreach ($nodeFinder->findInstanceOf($classNode, Node\Stmt\ClassMethod::class) as $methodNode) { @@ -81,9 +82,10 @@ public function extractMethodBodies(string $className): array public function extractFunctionBody(string $name): ?string { /** @var Node\Stmt\Function_ $functionNode */ - $functionNode = (new NodeFinder)->findFirst($this->statements, function (Node $node) use ($name) { - return $node instanceof Node\Stmt\Function_ && $node->namespacedName->toString() === $name; - }); + $functionNode = (new NodeFinder)->findFirst( + $this->statements, + fn(Node $node) => $node instanceof Node\Stmt\Function_ && $node->namespacedName->toString() === $name, + ); return $this->getReformattedContents($functionNode->stmts, 1); } @@ -105,9 +107,11 @@ private function prepareReplacements(array $statements): array (new NodeFinder)->find($statements, function (Node $node) use (&$replacements, $start) { if ($node instanceof Node\Name\FullyQualified) { if ($node->getAttribute('originalName') instanceof Node\Name) { - $of = $node->getAttribute('parent') instanceof Node\Expr\ConstFetch - ? PhpNamespace::NAME_CONSTANT - : ($node->getAttribute('parent') instanceof Node\Expr\FuncCall ? PhpNamespace::NAME_FUNCTION : PhpNamespace::NAME_NORMAL); + $of = match (true) { + $node->getAttribute('parent') instanceof Node\Expr\ConstFetch => PhpNamespace::NAME_CONSTANT, + $node->getAttribute('parent') instanceof Node\Expr\FuncCall => PhpNamespace::NAME_FUNCTION, + default => PhpNamespace::NAME_NORMAL, + }; $replacements[] = [ $node->getStartFilePos() - $start, $node->getEndFilePos() - $start, @@ -117,7 +121,7 @@ private function prepareReplacements(array $statements): array } elseif ($node instanceof Node\Scalar\String_ || $node instanceof Node\Scalar\EncapsedStringPart) { // multi-line strings => singleline $token = $this->getNodeContents($node); - if (strpos($token, "\n") !== false) { + if (str_contains($token, "\n")) { $quote = $node instanceof Node\Scalar\String_ ? '"' : ''; $replacements[] = [ $node->getStartFilePos() - $start, @@ -147,9 +151,7 @@ private function prepareReplacements(array $statements): array private function performReplacements(string $s, array $replacements): string { - usort($replacements, function ($a, $b) { // sort by position in file - return $b[0] <=> $a[0]; - }); + usort($replacements, fn($a, $b) => $b[0] <=> $a[0]); foreach ($replacements as [$start, $end, $replacement]) { $s = substr_replace($s, $replacement, $start, $end - $start + 1); @@ -171,38 +173,25 @@ public function enterNode(Node $node) }; $visitor->callback = function (Node $node) use (&$class, &$namespace, $phpFile) { - if ($node instanceof Node\Stmt\DeclareDeclare && $node->key->name === 'strict_types') { - $phpFile->setStrictTypes((bool) $node->value->value); - } elseif ($node instanceof Node\Stmt\Namespace_) { - $namespace = $node->name ? $node->name->toString() : ''; - } elseif ($node instanceof Node\Stmt\Use_) { - $this->addUseToNamespace($node, $phpFile->addNamespace($namespace)); - } elseif ($node instanceof Node\Stmt\Class_) { - if (!$node->name) { - return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; - } - - $class = $this->addClassToFile($phpFile, $node); - } elseif ($node instanceof Node\Stmt\Interface_) { - $class = $this->addInterfaceToFile($phpFile, $node); - } elseif ($node instanceof Node\Stmt\Trait_) { - $class = $this->addTraitToFile($phpFile, $node); - } elseif ($node instanceof Node\Stmt\Enum_) { - $class = $this->addEnumToFile($phpFile, $node); - } elseif ($node instanceof Node\Stmt\Function_) { - $this->addFunctionToFile($phpFile, $node); - } elseif ($node instanceof Node\Stmt\TraitUse) { - $this->addTraitToClass($class, $node); - } elseif ($node instanceof Node\Stmt\Property) { - $this->addPropertyToClass($class, $node); - } elseif ($node instanceof Node\Stmt\ClassMethod) { - $this->addMethodToClass($class, $node); - } elseif ($node instanceof Node\Stmt\ClassConst) { - $this->addConstantToClass($class, $node); - } elseif ($node instanceof Node\Stmt\EnumCase) { - $this->addEnumCaseToClass($class, $node); + if ($node instanceof Node\Stmt\Class_ && !$node->name) { + return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; } - + match (true) { + $node instanceof Node\Stmt\DeclareDeclare && $node->key->name === 'strict_types' => $phpFile->setStrictTypes((bool) $node->value->value), + $node instanceof Node\Stmt\Namespace_ => $namespace = $node->name?->toString(), + $node instanceof Node\Stmt\Use_ => $this->addUseToNamespace($node, $phpFile->addNamespace($namespace)), + $node instanceof Node\Stmt\Class_ => $class = $this->addClassToFile($phpFile, $node), + $node instanceof Node\Stmt\Interface_ => $class = $this->addInterfaceToFile($phpFile, $node), + $node instanceof Node\Stmt\Trait_ => $class = $this->addTraitToFile($phpFile, $node), + $node instanceof Node\Stmt\Enum_ => $class = $this->addEnumToFile($phpFile, $node), + $node instanceof Node\Stmt\Function_ => $this->addFunctionToFile($phpFile, $node), + $node instanceof Node\Stmt\TraitUse => $this->addTraitToClass($class, $node), + $node instanceof Node\Stmt\Property => $this->addPropertyToClass($class, $node), + $node instanceof Node\Stmt\ClassMethod => $this->addMethodToClass($class, $node), + $node instanceof Node\Stmt\ClassConst => $this->addConstantToClass($class, $node), + $node instanceof Node\Stmt\EnumCase => $this->addEnumCaseToClass($class, $node), + default => null, + }; if ($node instanceof Node\FunctionLike) { return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; } @@ -227,7 +216,7 @@ private function addUseToNamespace(Node\Stmt\Use_ $node, PhpNamespace $namespace $node::TYPE_CONSTANT => PhpNamespace::NAME_CONSTANT, ][$node->type]; foreach ($node->uses as $use) { - $namespace->addUse($use->name->toString(), $use->alias ? $use->alias->toString() : null, $of); + $namespace->addUse($use->name->toString(), $use->alias?->toString(), $of); } } @@ -360,7 +349,7 @@ private function addConstantToClass(ClassType $class, Node\Stmt\ClassConst $node private function addEnumCaseToClass(ClassType $class, Node\Stmt\EnumCase $node) { - $case = $class->addCase($node->name->toString(), $node->expr ? $node->expr->value : null); + $case = $class->addCase($node->name->toString(), $node->expr?->value); $this->addCommentAndAttributes($case, $node); } diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 0a2717fe..84169d72 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -27,7 +27,7 @@ final class Factory public function fromClassReflection( \ReflectionClass $from, bool $withBodies = false, - bool $materializeTraits = true + bool $materializeTraits = true, ): ClassType { if ($withBodies && $from->isAnonymous()) { throw new Nette\NotSupportedException('The $withBodies parameter cannot be used for anonymous functions.'); @@ -50,9 +50,7 @@ public function fromClassReflection( $ifaces = $from->getInterfaceNames(); foreach ($ifaces as $iface) { - $ifaces = array_filter($ifaces, function (string $item) use ($iface): bool { - return !is_subclass_of($iface, $item); - }); + $ifaces = array_filter($ifaces, fn(string $item): bool => !is_subclass_of($iface, $item)); } if ($from->isInterface()) { @@ -99,7 +97,7 @@ public function fromClassReflection( if ($withBodies) { $realMethodClass = $realMethod->getDeclaringClass(); $bodies = &$this->bodyCache[$realMethodClass->name]; - $bodies = $bodies ?? $this->getExtractor($realMethodClass)->extractMethodBodies($realMethodClass->name); + $bodies ??= $this->getExtractor($realMethodClass)->extractMethodBodies($realMethodClass->name); if (isset($bodies[$realMethod->name])) { $m->setBody($bodies[$realMethod->name]); } @@ -298,18 +296,14 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property public function fromObject(object $obj): Literal { - return new Literal('new ' . get_class($obj) . '(/* unknown */)'); + return new Literal('new ' . $obj::class . '(/* unknown */)'); } public function fromClassCode(string $code): ClassType { $classes = $this->fromCode($code)->getClasses(); - if (!$classes) { - throw new Nette\InvalidStateException('The code does not contain any class.'); - } - - return reset($classes); + return reset($classes) ?: throw new Nette\InvalidStateException('The code does not contain any class.'); } diff --git a/src/PhpGenerator/GlobalFunction.php b/src/PhpGenerator/GlobalFunction.php index 7e40f363..f1c0012b 100644 --- a/src/PhpGenerator/GlobalFunction.php +++ b/src/PhpGenerator/GlobalFunction.php @@ -33,7 +33,7 @@ public static function from(string $function, bool $withBody = false): self public static function withBodyFrom(string $function): self { - return (new Factory)->fromFunctionReflection(new \ReflectionFunction($function), true); + return (new Factory)->fromFunctionReflection(new \ReflectionFunction($function), withBody: true); } diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index b51d7001..78b48d86 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -77,7 +77,7 @@ public function addNamespace($namespace): PhpNamespace $res = $this->namespaces[$namespace->getName()] = $namespace; } elseif (is_string($namespace)) { - $res = $this->namespaces[$namespace] = $this->namespaces[$namespace] ?? new PhpNamespace($namespace); + $res = $this->namespaces[$namespace] ??= new PhpNamespace($namespace); } else { throw new Nette\InvalidArgumentException('Argument must be string|PhpNamespace.'); diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 0b73d1b2..97571e47 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -122,7 +122,7 @@ public function addUse(string $name, ?string $alias = null, string $of = self::N $lower = strtolower($alias); if (isset($aliases[$lower]) && strcasecmp($aliases[$lower], $name) !== 0) { throw new InvalidStateException( - "Alias '$alias' used already for '{$aliases[$lower]}', cannot use for '$name'." + "Alias '$alias' used already for '{$aliases[$lower]}', cannot use for '$name'.", ); } elseif (isset($used[$lower])) { throw new Nette\InvalidStateException("Name '$alias' used already for '$this->name\\{$used[$lower]->getName()}'."); @@ -154,8 +154,8 @@ public function getUses(string $of = self::NAME_NORMAL): array asort($this->aliases[$of]); return array_filter( $this->aliases[$of], - function ($name, $alias) { return strcasecmp(($this->name ? $this->name . '\\' : '') . $alias, $name); }, - ARRAY_FILTER_USE_BOTH + fn($name, $alias) => strcasecmp(($this->name ? $this->name . '\\' : '') . $alias, $name), + ARRAY_FILTER_USE_BOTH, ); } @@ -190,7 +190,7 @@ public function resolveName(string $name, string $of = self::NAME_NORMAL): strin public function simplifyType(string $type, string $of = self::NAME_NORMAL): string { - return preg_replace_callback('~[\w\x7f-\xff\\\\]+~', function ($m) use ($of) { return $this->simplifyName($m[0], $of); }, $type); + return preg_replace_callback('~[\w\x7f-\xff\\\\]+~', fn($m) => $this->simplifyName($m[0], $of), $type); } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 3ff5fc24..8360aaa0 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -82,7 +82,7 @@ public function printClosure(Closure $closure, ?PhpNamespace $namespace = null): : $tmp; $body = Helpers::simplifyTaggedNames($closure->getBody(), $this->namespace); - return self::printAttributes($closure->getAttributes(), true) + return self::printAttributes($closure->getAttributes(), inline: true) . 'function ' . ($closure->getReturnReference() ? '&' : '') . $this->printParameters($closure) @@ -147,7 +147,7 @@ public function printClass(ClassType $class, ?PhpNamespace $namespace = null): s $class->validate(); $resolver = $this->namespace ? [$namespace, 'simplifyType'] - : function ($s) { return $s; }; + : fn($s) => $s; $traits = []; foreach ($class->getTraitResolutions() as $trait) { @@ -228,7 +228,7 @@ public function printClass(ClassType $class, ?PhpNamespace $namespace = null): s . ($class->getImplements() ? 'implements ' . implode(', ', array_map($resolver, $class->getImplements())) . ' ' : '') . ($class->getName() ? "\n" : '') . "{\n" . ($members ? $this->indent(implode("\n", $members)) : '') - . '}' + . '}', ) . ($class->getName() ? "\n" : ''); } @@ -277,7 +277,7 @@ public function printFile(PhpFile $file): string . ($file->getComment() ? "\n" . Helpers::formatDocComment($file->getComment() . "\n") : '') . "\n" . ($file->hasStrictTypes() ? "declare(strict_types=1);\n\n" : '') - . implode("\n\n", $namespaces) + . implode("\n\n", $namespaces), ) . "\n"; } @@ -317,7 +317,7 @@ protected function printParameters($function, int $column = 0): string $promoted = $param instanceof PromotedParameter ? $param : null; $params[] = ($promoted ? Helpers::formatDocComment((string) $promoted->getComment()) : '') - . ($attrs = self::printAttributes($param->getAttributes(), true)) + . ($attrs = self::printAttributes($param->getAttributes(), inline: true)) . ($promoted ? ($promoted->getVisibility() ?: 'public') . ($promoted->isReadOnly() && $type ? ' readonly' : '') @@ -381,7 +381,7 @@ private function printAttributes(array $attrs, bool $inline = false): string foreach ($attrs as $attr) { $args = $this->dumper->format('...?:', $attr->getArguments()); $args = Helpers::simplifyTaggedNames($args, $this->namespace); - $items[] = $this->printType($attr->getName(), false) . ($args ? "($args)" : ''); + $items[] = $this->printType($attr->getName(), nullable: false) . ($args ? "($args)" : ''); } return $inline diff --git a/src/PhpGenerator/Type.php b/src/PhpGenerator/Type.php index f7568550..9a30ac6c 100644 --- a/src/PhpGenerator/Type.php +++ b/src/PhpGenerator/Type.php @@ -55,7 +55,7 @@ public static function intersection(string ...$types): string public static function getType($value): ?string { if (is_object($value)) { - return get_class($value); + return $value::class; } elseif (is_int($value)) { return self::INT; } elseif (is_float($value)) { diff --git a/tests/PhpGenerator/ClassType.from.trait.phpt b/tests/PhpGenerator/ClassType.from.trait.phpt index 436f3ed0..2b596f35 100644 --- a/tests/PhpGenerator/ClassType.from.trait.phpt +++ b/tests/PhpGenerator/ClassType.from.trait.phpt @@ -20,29 +20,21 @@ $classes = [ Class5::class, ]; -$res = array_map(function ($class) { - return ClassType::from($class); -}, $classes); +$res = array_map(fn($class) => ClassType::from($class), $classes); sameFile(__DIR__ . '/expected/ClassType.from.trait-materialize.expect', implode("\n", $res)); -$res = array_map(function ($class) { - return ClassType::withBodiesFrom($class); -}, $classes); +$res = array_map(fn($class) => ClassType::withBodiesFrom($class), $classes); sameFile(__DIR__ . '/expected/ClassType.from.trait-materialize.bodies.expect', implode("\n", $res)); -$res = array_map(function ($class) { - return ClassType::from($class, /*withBodies:*/ false, /*materializeTraits:*/ false); -}, $classes); +$res = array_map(fn($class) => ClassType::from($class, materializeTraits: false), $classes); sameFile(__DIR__ . '/expected/ClassType.from.trait-use.expect', implode("\n", $res)); -$res = array_map(function ($class) { - return ClassType::from($class, /*withBodies:*/ true, /*materializeTraits:*/ false); -}, $classes); +$res = array_map(fn($class) => ClassType::from($class, withBodies: true, materializeTraits: false), $classes); sameFile(__DIR__ . '/expected/ClassType.from.trait-use.bodies.expect', implode("\n", $res)); diff --git a/tests/PhpGenerator/ClassType.fromCode.phpt b/tests/PhpGenerator/ClassType.fromCode.phpt index 786d6ea8..c21ff8d8 100644 --- a/tests/PhpGenerator/ClassType.fromCode.phpt +++ b/tests/PhpGenerator/ClassType.fromCode.phpt @@ -11,16 +11,15 @@ require __DIR__ . '/../bootstrap.php'; $class = ClassType::fromCode(file_get_contents(__DIR__ . '/fixtures/classes.php')); Assert::type(ClassType::class, $class); Assert::match(<<<'XX' -/** - * Interface - * @author John Doe - */ -interface Interface1 -{ - function func1(); -} -XX -, (string) $class); + /** + * Interface + * @author John Doe + */ + interface Interface1 + { + function func1(); + } + XX, (string) $class); Assert::exception(function () { diff --git a/tests/PhpGenerator/Closure.attributes.80.phpt b/tests/PhpGenerator/Closure.attributes.80.phpt index 918d9b9b..aeacfcc2 100644 --- a/tests/PhpGenerator/Closure.attributes.80.phpt +++ b/tests/PhpGenerator/Closure.attributes.80.phpt @@ -18,5 +18,5 @@ $function = Closure::from($closure); same( '#[ExampleAttribute] function (stdClass $a, $b = null) { }', - (string) $function + (string) $function, ); diff --git a/tests/PhpGenerator/Closure.long.phpt b/tests/PhpGenerator/Closure.long.phpt index e1d1ad94..a6b0a4db 100644 --- a/tests/PhpGenerator/Closure.long.phpt +++ b/tests/PhpGenerator/Closure.long.phpt @@ -54,5 +54,5 @@ same( ) { return null; }', - (string) $function + (string) $function, ); diff --git a/tests/PhpGenerator/Closure.phpt b/tests/PhpGenerator/Closure.phpt index cc0c57df..f801e845 100644 --- a/tests/PhpGenerator/Closure.phpt +++ b/tests/PhpGenerator/Closure.phpt @@ -24,7 +24,7 @@ same( 'function &($a, $b) use ($this, &$vars) { return $a + $b; }', - (string) $function + (string) $function, ); @@ -39,7 +39,7 @@ same( 'function &($a, $b) use ($this) { return $a + $b; }', - (string) $function + (string) $function, ); @@ -61,7 +61,7 @@ same( 'function () use ($this): array { return []; }', - (string) $function + (string) $function, ); @@ -74,7 +74,7 @@ same( '#[ExampleAttribute] function () { return $a + $b; }', - (string) $function + (string) $function, ); @@ -84,5 +84,5 @@ $function = Closure::from($closure); same( 'function (stdClass $a, $b = null) { }', - (string) $function + (string) $function, ); diff --git a/tests/PhpGenerator/Dumper.dump().indent.phpt b/tests/PhpGenerator/Dumper.dump().indent.phpt index 1f359af7..1ad81c65 100644 --- a/tests/PhpGenerator/Dumper.dump().indent.phpt +++ b/tests/PhpGenerator/Dumper.dump().indent.phpt @@ -55,5 +55,5 @@ same( 1, ], ]", - $dumper->dump(['multi' => new Literal("[\n1,\n]\n")]) + $dumper->dump(['multi' => new Literal("[\n1,\n]\n")]), ); diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index be0ca966..bdfc77e3 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -39,7 +39,7 @@ Assert::same('\'$"\\ \x00\'', $dumper->dump('$"\\ \x00')); // no escape Assert::same('"\\$\\"\\\\ \x00"', $dumper->dump("$\"\\ \x00")); Assert::same( "'I\u{F1}t\u{EB}rn\u{E2}ti\u{F4}n\u{E0}liz\u{E6}ti\u{F8}n'", - $dumper->dump("I\u{F1}t\u{EB}rn\u{E2}ti\u{F4}n\u{E0}liz\u{E6}ti\u{F8}n") // Iñtërnâtiônàlizætiøn + $dumper->dump("I\u{F1}t\u{EB}rn\u{E2}ti\u{F4}n\u{E0}liz\u{E6}ti\u{F8}n"), // Iñtërnâtiônàlizætiøn ); Assert::same('"\rHello \$"', $dumper->dump("\rHello $")); Assert::same("'He\\llo'", $dumper->dump('He\llo')); @@ -65,12 +65,12 @@ Assert::same("[0 => 'a', -2 => 'b', 1 => 'c']", $dumper->dump(['a', -2 => 'b', 1 // stdClass Assert::same( "(object) [\n\t'a' => 1,\n\t'b' => 2,\n]", - $dumper->dump((object) ['a' => 1, 'b' => 2]) + $dumper->dump((object) ['a' => 1, 'b' => 2]), ); Assert::same( "(object) [\n\t'a' => (object) [\n\t\t'b' => 2,\n\t],\n]", - $dumper->dump((object) ['a' => (object) ['b' => 2]]) + $dumper->dump((object) ['a' => (object) ['b' => 2]]), ); @@ -86,7 +86,7 @@ class Test Assert::same( "\\Nette\\PhpGenerator\\Dumper::createObject('Test', [\n\t'a' => 1,\n\t\"\\x00*\\x00b\" => 2,\n\t\"\\x00Test\\x00c\" => 3,\n])", - $dumper->dump(new Test) + $dumper->dump(new Test), ); Assert::equal(new Test, eval('return ' . $dumper->dump(new Test) . ';')); @@ -113,7 +113,7 @@ Assert::same( PHP_VERSION_ID < 80100 ? "\\Nette\\PhpGenerator\\Dumper::createObject('Test2', [\n\t\"\\x00Test2\\x00c\" => 4,\n\t'a' => 1,\n\t\"\\x00*\\x00b\" => 2,\n])" : "\\Nette\\PhpGenerator\\Dumper::createObject('Test2', [\n\t'a' => 1,\n\t\"\\x00*\\x00b\" => 2,\n\t\"\\x00Test2\\x00c\" => 4,\n])", - $dumper->dump(new Test2) + $dumper->dump(new Test2), ); Assert::equal(new Test2, eval('return ' . $dumper->dump(new Test2) . ';')); @@ -131,14 +131,14 @@ Assert::same( PHP_VERSION_ID < 80100 ? "\\Closure::fromCallable('strlen')" : 'strlen(...)', - $dumper->dump(Closure::fromCallable('strlen')) + $dumper->dump(Closure::fromCallable('strlen')), ); Assert::same( PHP_VERSION_ID < 80100 ? "\\Closure::fromCallable(['Nette\\PhpGenerator\\ClassType', 'from'])" : 'Nette\PhpGenerator\ClassType::from(...)', - $dumper->dump(Closure::fromCallable([Nette\PhpGenerator\ClassType::class, 'from'])) + $dumper->dump(Closure::fromCallable([Nette\PhpGenerator\ClassType::class, 'from'])), ); Assert::exception(function () { @@ -179,11 +179,11 @@ class TestDateTime extends DateTime Assert::same( "new \\DateTime('2016-06-22 20:52:43.123400', new \\DateTimeZone('Europe/Prague'))", - $dumper->dump(new DateTime('2016-06-22 20:52:43.1234', new DateTimeZone('Europe/Prague'))) + $dumper->dump(new DateTime('2016-06-22 20:52:43.1234', new DateTimeZone('Europe/Prague'))), ); Assert::same( "new \\DateTimeImmutable('2016-06-22 20:52:43.123400', new \\DateTimeZone('Europe/Prague'))", - $dumper->dump(new DateTimeImmutable('2016-06-22 20:52:43.1234', new DateTimeZone('Europe/Prague'))) + $dumper->dump(new DateTimeImmutable('2016-06-22 20:52:43.1234', new DateTimeZone('Europe/Prague'))), ); same( "\\Nette\\PhpGenerator\\Dumper::createObject('TestDateTime', [ @@ -191,5 +191,5 @@ same( 'timezone_type' => 3, 'timezone' => 'Europe/Prague', ])", - $dumper->dump(new TestDateTime('2016-06-22 20:52:43.1234', new DateTimeZone('Europe/Prague'))) + $dumper->dump(new TestDateTime('2016-06-22 20:52:43.1234', new DateTimeZone('Europe/Prague'))), ); diff --git a/tests/PhpGenerator/Dumper.dump().wrap.phpt b/tests/PhpGenerator/Dumper.dump().wrap.phpt index 56452d12..eb429109 100644 --- a/tests/PhpGenerator/Dumper.dump().wrap.phpt +++ b/tests/PhpGenerator/Dumper.dump().wrap.phpt @@ -27,7 +27,7 @@ same( $dumper->dump([ 'a' => [1, 2, 3], 'aaaaaaaaa' => [1, 2, 3], - ]) + ]), ); same( @@ -40,7 +40,7 @@ same( $dumper->dump([ 'single' => new Literal('1 + 2'), 'multi' => new Literal("[\n\t1,\n]\n"), - ]) + ]), ); same( @@ -55,7 +55,7 @@ same( $dumper->dump((object) [ 'a' => [1, 2, 3], 'aaaaaaaaa' => [1, 2, 3], - ]) + ]), ); diff --git a/tests/PhpGenerator/Dumper.format().phpt b/tests/PhpGenerator/Dumper.format().phpt index c3cde14b..da1e31a2 100644 --- a/tests/PhpGenerator/Dumper.format().phpt +++ b/tests/PhpGenerator/Dumper.format().phpt @@ -56,7 +56,7 @@ same( 35, 36 )', - $dumper->format('func(?*)', range(10, 36)) + $dumper->format('func(?*)', range(10, 36)), ); Assert::exception(function () { diff --git a/tests/PhpGenerator/Extractor.extractAll.phpt b/tests/PhpGenerator/Extractor.extractAll.phpt index c680d860..fb0d0618 100644 --- a/tests/PhpGenerator/Extractor.extractAll.phpt +++ b/tests/PhpGenerator/Extractor.extractAll.phpt @@ -30,58 +30,55 @@ 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(); + XX))->extractAll(); Assert::type(Nette\PhpGenerator\PhpFile::class, $file); Assert::match(<<<'XX' -extractFunctionBody('NS\bar1') + $extractor->extractFunctionBody('NS\bar1'), ); diff --git a/tests/PhpGenerator/Factory.fromClassCode.phpt b/tests/PhpGenerator/Factory.fromClassCode.phpt index e3d17d07..fc444db3 100644 --- a/tests/PhpGenerator/Factory.fromClassCode.phpt +++ b/tests/PhpGenerator/Factory.fromClassCode.phpt @@ -14,13 +14,12 @@ $factory = new Factory; $class = $factory->fromClassCode(file_get_contents(__DIR__ . '/fixtures/classes.php')); Assert::type(Nette\PhpGenerator\ClassType::class, $class); Assert::match(<<<'XX' -/** - * Interface - * @author John Doe - */ -interface Interface1 -{ - function func1(); -} -XX -, (string) $class); + /** + * Interface + * @author John Doe + */ + interface Interface1 + { + function func1(); + } + XX, (string) $class); diff --git a/tests/PhpGenerator/GlobalFunction.attributes.80.phpt b/tests/PhpGenerator/GlobalFunction.attributes.80.phpt index 9fc459ff..21aeb842 100644 --- a/tests/PhpGenerator/GlobalFunction.attributes.80.phpt +++ b/tests/PhpGenerator/GlobalFunction.attributes.80.phpt @@ -28,5 +28,5 @@ function func(stdClass $a, $b = null) { } ', - (string) $function + (string) $function, ); diff --git a/tests/PhpGenerator/GlobalFunction.phpt b/tests/PhpGenerator/GlobalFunction.phpt index 7cf3e5cb..fa435f94 100644 --- a/tests/PhpGenerator/GlobalFunction.phpt +++ b/tests/PhpGenerator/GlobalFunction.phpt @@ -23,7 +23,7 @@ function test() return $a + $b; } ', - (string) $function + (string) $function, ); @@ -44,20 +44,19 @@ function func(stdClass $a, $b = null) { } ', - (string) $function + (string) $function, ); $function = GlobalFunction::withBodyFrom('func'); same(<<<'XX' -/** - * global - */ -function func(stdClass $a, $b = null) -{ - echo \sprintf('hello, %s', 'world'); - return 1; -} - -XX -, (string) $function); + /** + * global + */ + function func(stdClass $a, $b = null) + { + echo \sprintf('hello, %s', 'world'); + return 1; + } + + XX, (string) $function); diff --git a/tests/PhpGenerator/Method.longParams.phpt b/tests/PhpGenerator/Method.longParams.phpt index eec4abe1..eb723a2a 100644 --- a/tests/PhpGenerator/Method.longParams.phpt +++ b/tests/PhpGenerator/Method.longParams.phpt @@ -32,5 +32,5 @@ same( return null; } ', - (string) $method + (string) $method, ); diff --git a/tests/PhpGenerator/Method.returnTypes.phpt b/tests/PhpGenerator/Method.returnTypes.phpt index 68527385..10b67c68 100644 --- a/tests/PhpGenerator/Method.returnTypes.phpt +++ b/tests/PhpGenerator/Method.returnTypes.phpt @@ -48,7 +48,7 @@ namespace return new Foo(); } ', - (string) $method + (string) $method, ); } diff --git a/tests/PhpGenerator/Method.scalarParameters.phpt b/tests/PhpGenerator/Method.scalarParameters.phpt index fac07279..0b11c876 100644 --- a/tests/PhpGenerator/Method.scalarParameters.phpt +++ b/tests/PhpGenerator/Method.scalarParameters.phpt @@ -48,5 +48,5 @@ same( return null; } ', - (string) $method + (string) $method, ); diff --git a/tests/PhpGenerator/Method.variadics.phpt b/tests/PhpGenerator/Method.variadics.phpt index 287a05ab..eace3529 100644 --- a/tests/PhpGenerator/Method.variadics.phpt +++ b/tests/PhpGenerator/Method.variadics.phpt @@ -45,7 +45,7 @@ same( return 42; } ', - (string) $method + (string) $method, ); @@ -61,7 +61,7 @@ same( return 42; } ', - (string) $method + (string) $method, ); @@ -79,7 +79,7 @@ same( return 42; } ', - (string) $method + (string) $method, ); @@ -95,7 +95,7 @@ same( return 42; } ', - (string) $method + (string) $method, ); @@ -111,5 +111,5 @@ same( return 42; } ', - (string) $method + (string) $method, ); From c181ffc153263f4256f7d76eeb8e119ffc21fd32 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 23 Sep 2021 19:26:51 +0200 Subject: [PATCH 095/266] added property typehints --- readme.md | 8 +++--- src/PhpGenerator/Attribute.php | 7 ++--- src/PhpGenerator/ClassType.php | 30 +++++++++----------- src/PhpGenerator/Closure.php | 2 +- src/PhpGenerator/Constant.php | 7 ++--- src/PhpGenerator/Dumper.php | 11 ++------ src/PhpGenerator/EnumCase.php | 3 +- src/PhpGenerator/Extractor.php | 6 ++-- src/PhpGenerator/Factory.php | 4 +-- src/PhpGenerator/Literal.php | 15 +++------- src/PhpGenerator/Method.php | 11 ++------ src/PhpGenerator/Parameter.php | 19 ++++--------- src/PhpGenerator/PhpFile.php | 6 ++-- src/PhpGenerator/PhpNamespace.php | 12 ++++---- src/PhpGenerator/Printer.php | 31 ++++++--------------- src/PhpGenerator/PromotedParameter.php | 3 +- src/PhpGenerator/Property.php | 23 ++++----------- src/PhpGenerator/PsrPrinter.php | 7 ++--- src/PhpGenerator/TraitUse.php | 3 +- src/PhpGenerator/Traits/AttributeAware.php | 2 +- src/PhpGenerator/Traits/CommentAware.php | 3 +- src/PhpGenerator/Traits/FunctionLike.php | 21 ++++---------- src/PhpGenerator/Traits/NameAware.php | 3 +- src/PhpGenerator/Traits/VisibilityAware.php | 4 +-- 24 files changed, 79 insertions(+), 162 deletions(-) diff --git a/readme.md b/readme.md index 04387ff1..c05c463d 100644 --- a/readme.md +++ b/readme.md @@ -772,9 +772,9 @@ Need to customize printer behavior? Create your own by inheriting the `Printer` ```php class MyPrinter extends Nette\PhpGenerator\Printer { - protected $indentation = "\t"; - protected $linesBetweenProperties = 0; - protected $linesBetweenMethods = 1; - protected $returnTypeColon = ': '; + protected string $indentation = "\t"; + protected int $linesBetweenProperties = 0; + protected int $linesBetweenMethods = 1; + protected string $returnTypeColon = ': '; } ``` diff --git a/src/PhpGenerator/Attribute.php b/src/PhpGenerator/Attribute.php index f02d3da0..ba2ebc42 100644 --- a/src/PhpGenerator/Attribute.php +++ b/src/PhpGenerator/Attribute.php @@ -19,11 +19,8 @@ final class Attribute { use Nette\SmartObject; - /** @var string */ - private $name; - - /** @var array */ - private $args; + private string $name; + private array $args; public function __construct(string $name, array $args) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 40ef58b1..e3a2229d 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -35,41 +35,37 @@ final class ClassType VISIBILITY_PROTECTED = 'protected', VISIBILITY_PRIVATE = 'private'; - /** @var PhpNamespace|null */ - private $namespace; + private ?PhpNamespace $namespace; - /** @var string|null */ - private $name; + private ?string $name; - /** @var string class|interface|trait */ - private $type = self::TYPE_CLASS; + /** class|interface|trait */ + private string $type = self::TYPE_CLASS; - /** @var bool */ - private $final = false; + private bool $final = false; - /** @var bool */ - private $abstract = false; + private bool $abstract = false; /** @var string|string[] */ - private $extends = []; + private string|array $extends = []; /** @var string[] */ - private $implements = []; + private array $implements = []; /** @var TraitUse[] */ - private $traits = []; + private array $traits = []; /** @var Constant[] name => Constant */ - private $consts = []; + private array $consts = []; /** @var Property[] name => Property */ - private $properties = []; + private array $properties = []; /** @var Method[] name => Method */ - private $methods = []; + private array $methods = []; /** @var EnumCase[] name => EnumCase */ - private $cases = []; + private array $cases = []; public static function class(?string $name): self diff --git a/src/PhpGenerator/Closure.php b/src/PhpGenerator/Closure.php index 636ed271..4c7e2023 100644 --- a/src/PhpGenerator/Closure.php +++ b/src/PhpGenerator/Closure.php @@ -24,7 +24,7 @@ final class Closure use Traits\AttributeAware; /** @var Parameter[] */ - private $uses = []; + private array $uses = []; public static function from(\Closure $closure): self diff --git a/src/PhpGenerator/Constant.php b/src/PhpGenerator/Constant.php index 3c1de300..3c77ff70 100644 --- a/src/PhpGenerator/Constant.php +++ b/src/PhpGenerator/Constant.php @@ -23,11 +23,8 @@ final class Constant use Traits\CommentAware; use Traits\AttributeAware; - /** @var mixed */ - private $value; - - /** @var bool */ - private $final = false; + private mixed $value; + private bool $final = false; /** @return static */ diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 5f3fbfa2..32de7fd0 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -19,14 +19,9 @@ final class Dumper { private const INDENT_LENGTH = 4; - /** @var int */ - public $maxDepth = 50; - - /** @var int */ - public $wrapLength = 120; - - /** @var string */ - public $indentation = "\t"; + public int $maxDepth = 50; + public int $wrapLength = 120; + public string $indentation = "\t"; /** diff --git a/src/PhpGenerator/EnumCase.php b/src/PhpGenerator/EnumCase.php index 3f25467a..086c8711 100644 --- a/src/PhpGenerator/EnumCase.php +++ b/src/PhpGenerator/EnumCase.php @@ -22,8 +22,7 @@ final class EnumCase use Traits\CommentAware; use Traits\AttributeAware; - /** @var string|int|null */ - private $value; + private string|int|null $value = null; /** @return static */ diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 347d7570..59935436 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -24,9 +24,9 @@ final class Extractor { use Nette\SmartObject; - private $code; - private $statements; - private $printer; + private string $code; + private array $statements; + private PhpParser\PrettyPrinterAbstract $printer; public function __construct(string $code) diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 84169d72..ad006331 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -20,8 +20,8 @@ final class Factory { use Nette\SmartObject; - private $bodyCache = []; - private $extractorCache = []; + private array $bodyCache = []; + private array $extractorCache = []; public function fromClassReflection( diff --git a/src/PhpGenerator/Literal.php b/src/PhpGenerator/Literal.php index 386b66aa..9f95488e 100644 --- a/src/PhpGenerator/Literal.php +++ b/src/PhpGenerator/Literal.php @@ -15,17 +15,10 @@ */ class Literal { - /** @var string */ - private $value; - - /** @var ?array */ - private $args; - - - public function __construct(string $value, ?array $args = null) - { - $this->value = $value; - $this->args = $args; + public function __construct( + private string $value, + private ?array $args = null, + ) { } diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index 0f57578d..9725db63 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -26,14 +26,9 @@ final class Method use Traits\CommentAware; use Traits\AttributeAware; - /** @var bool */ - private $static = false; - - /** @var bool */ - private $final = false; - - /** @var bool */ - private $abstract = false; + private bool $static = false; + private bool $final = false; + private bool $abstract = false; /** diff --git a/src/PhpGenerator/Parameter.php b/src/PhpGenerator/Parameter.php index 706c8d86..478d336e 100644 --- a/src/PhpGenerator/Parameter.php +++ b/src/PhpGenerator/Parameter.php @@ -24,20 +24,11 @@ class Parameter use Traits\NameAware; use Traits\AttributeAware; - /** @var bool */ - private $reference = false; - - /** @var string|null */ - private $type; - - /** @var bool */ - private $nullable = false; - - /** @var bool */ - private $hasDefaultValue = false; - - /** @var mixed */ - private $defaultValue; + private bool $reference = false; + private ?string $type = null; + private bool $nullable = false; + private bool $hasDefaultValue = false; + private mixed $defaultValue = null; /** @return static */ diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index 78b48d86..e6db7ce1 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -26,10 +26,8 @@ final class PhpFile use Traits\CommentAware; /** @var PhpNamespace[] */ - private $namespaces = []; - - /** @var bool */ - private $strictTypes = false; + private array $namespaces = []; + private bool $strictTypes = false; public static function fromCode(string $code): self diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 97571e47..80ea024d 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -30,24 +30,22 @@ final class PhpNamespace NAME_FUNCTION = 'f', NAME_CONSTANT = 'c'; - /** @var string */ - private $name; + private string $name; - /** @var bool */ - private $bracketedSyntax = false; + private bool $bracketedSyntax = false; /** @var string[][] */ - private $aliases = [ + private array $aliases = [ self::NAME_NORMAL => [], self::NAME_FUNCTION => [], self::NAME_CONSTANT => [], ]; /** @var ClassType[] */ - private $classes = []; + private array $classes = []; /** @var GlobalFunction[] */ - private $functions = []; + private array $functions = []; public function __construct(string $name) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 8360aaa0..27800e7f 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -20,29 +20,14 @@ class Printer { use Nette\SmartObject; - /** @var int */ - public $wrapLength = 120; - - /** @var string */ - protected $indentation = "\t"; - - /** @var int */ - protected $linesBetweenProperties = 0; - - /** @var int */ - protected $linesBetweenMethods = 2; - - /** @var string */ - protected $returnTypeColon = ': '; - - /** @var ?PhpNamespace */ - protected $namespace; - - /** @var ?Dumper */ - protected $dumper; - - /** @var bool */ - private $resolveTypes = true; + public int $wrapLength = 120; + protected string $indentation = "\t"; + protected int $linesBetweenProperties = 0; + protected int $linesBetweenMethods = 2; + protected string $returnTypeColon = ': '; + protected ?PhpNamespace $namespace = null; + protected ?Dumper $dumper; + private bool $resolveTypes = true; public function __construct() diff --git a/src/PhpGenerator/PromotedParameter.php b/src/PhpGenerator/PromotedParameter.php index 00b8a817..57e7f28c 100644 --- a/src/PhpGenerator/PromotedParameter.php +++ b/src/PhpGenerator/PromotedParameter.php @@ -20,8 +20,7 @@ final class PromotedParameter extends Parameter use Traits\VisibilityAware; use Traits\CommentAware; - /** @var bool */ - private $readOnly = false; + private bool $readOnly = false; /** @return static */ diff --git a/src/PhpGenerator/Property.php b/src/PhpGenerator/Property.php index 04c95e50..e05a8d16 100644 --- a/src/PhpGenerator/Property.php +++ b/src/PhpGenerator/Property.php @@ -26,23 +26,12 @@ final class Property use Traits\CommentAware; use Traits\AttributeAware; - /** @var mixed */ - private $value; - - /** @var bool */ - private $static = false; - - /** @var string|null */ - private $type; - - /** @var bool */ - private $nullable = false; - - /** @var bool */ - private $initialized = false; - - /** @var bool */ - private $readOnly = false; + private mixed $value = null; + private bool $static = false; + private ?string $type = null; + private bool $nullable = false; + private bool $initialized = false; + private bool $readOnly = false; /** @return static */ diff --git a/src/PhpGenerator/PsrPrinter.php b/src/PhpGenerator/PsrPrinter.php index aef0f7d0..efb3c618 100644 --- a/src/PhpGenerator/PsrPrinter.php +++ b/src/PhpGenerator/PsrPrinter.php @@ -15,9 +15,6 @@ */ final class PsrPrinter extends Printer { - /** @var string */ - protected $indentation = ' '; - - /** @var int */ - protected $linesBetweenMethods = 1; + protected string $indentation = ' '; + protected int $linesBetweenMethods = 1; } diff --git a/src/PhpGenerator/TraitUse.php b/src/PhpGenerator/TraitUse.php index 12514014..72bb87e4 100644 --- a/src/PhpGenerator/TraitUse.php +++ b/src/PhpGenerator/TraitUse.php @@ -21,8 +21,7 @@ final class TraitUse use Traits\NameAware; use Traits\CommentAware; - /** @var array */ - private $resolutions = []; + private array $resolutions = []; public function __construct(string $name) diff --git a/src/PhpGenerator/Traits/AttributeAware.php b/src/PhpGenerator/Traits/AttributeAware.php index d0195eb1..00ca0834 100644 --- a/src/PhpGenerator/Traits/AttributeAware.php +++ b/src/PhpGenerator/Traits/AttributeAware.php @@ -18,7 +18,7 @@ trait AttributeAware { /** @var Attribute[] */ - private $attributes = []; + private array $attributes = []; /** @return static */ diff --git a/src/PhpGenerator/Traits/CommentAware.php b/src/PhpGenerator/Traits/CommentAware.php index b5314056..2acc5062 100644 --- a/src/PhpGenerator/Traits/CommentAware.php +++ b/src/PhpGenerator/Traits/CommentAware.php @@ -15,8 +15,7 @@ */ trait CommentAware { - /** @var string|null */ - private $comment; + private ?string $comment = null; /** @return static */ diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index 7202df61..5213399c 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -20,23 +20,14 @@ */ trait FunctionLike { - /** @var string */ - private $body = ''; + private string $body = ''; /** @var Parameter[] */ - private $parameters = []; - - /** @var bool */ - private $variadic = false; - - /** @var string|null */ - private $returnType; - - /** @var bool */ - private $returnReference = false; - - /** @var bool */ - private $returnNullable = false; + private array $parameters = []; + private bool $variadic = false; + private ?string $returnType = null; + private bool $returnReference = false; + private bool $returnNullable = false; /** @return static */ diff --git a/src/PhpGenerator/Traits/NameAware.php b/src/PhpGenerator/Traits/NameAware.php index bb7e356a..7bac08d6 100644 --- a/src/PhpGenerator/Traits/NameAware.php +++ b/src/PhpGenerator/Traits/NameAware.php @@ -17,8 +17,7 @@ */ trait NameAware { - /** @var string */ - private $name; + private string $name; public function __construct(string $name) diff --git a/src/PhpGenerator/Traits/VisibilityAware.php b/src/PhpGenerator/Traits/VisibilityAware.php index 113953a4..3b5c676a 100644 --- a/src/PhpGenerator/Traits/VisibilityAware.php +++ b/src/PhpGenerator/Traits/VisibilityAware.php @@ -18,8 +18,8 @@ */ trait VisibilityAware { - /** @var string|null public|protected|private */ - private $visibility; + /** public|protected|private */ + private ?string $visibility = null; /** From 9b3e3afcb5b3de2309b77de4156d9fd432d35859 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Dec 2021 18:19:25 +0100 Subject: [PATCH 096/266] added PHP 8 typehints --- src/PhpGenerator/ClassType.php | 97 ++++++--------------- src/PhpGenerator/Closure.php | 3 +- src/PhpGenerator/Constant.php | 8 +- src/PhpGenerator/EnumCase.php | 3 +- src/PhpGenerator/Extractor.php | 9 +- src/PhpGenerator/Factory.php | 7 +- src/PhpGenerator/Method.php | 14 +-- src/PhpGenerator/Parameter.php | 24 ++--- src/PhpGenerator/PhpFile.php | 21 ++--- src/PhpGenerator/PhpNamespace.php | 19 ++-- src/PhpGenerator/Printer.php | 13 +-- src/PhpGenerator/PromotedParameter.php | 3 +- src/PhpGenerator/Property.php | 25 ++---- src/PhpGenerator/TraitUse.php | 2 +- src/PhpGenerator/Traits/AttributeAware.php | 6 +- src/PhpGenerator/Traits/CommentAware.php | 6 +- src/PhpGenerator/Traits/FunctionLike.php | 31 +++---- src/PhpGenerator/Traits/NameAware.php | 3 +- src/PhpGenerator/Traits/VisibilityAware.php | 12 +-- tests/PhpGenerator/ClassType.addMember.phpt | 6 -- 20 files changed, 96 insertions(+), 216 deletions(-) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index e3a2229d..bc47f040 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -92,20 +92,14 @@ public static function enum(string $name): self } - /** - * @param string|object $class - */ - public static function from($class, bool $withBodies = false, bool $materializeTraits = true): self + public static function from(string|object $class, bool $withBodies = false, bool $materializeTraits = true): self { return (new Factory) ->fromClassReflection(new \ReflectionClass($class), $withBodies, $materializeTraits); } - /** - * @param string|object $class - */ - public static function withBodiesFrom($class): self + public static function withBodiesFrom(string|object $class): self { return (new Factory) ->fromClassReflection(new \ReflectionClass($class), withBodies: true); @@ -139,8 +133,7 @@ public function getNamespace(): ?PhpNamespace } - /** @return static */ - public function setName(?string $name): self + public function setName(?string $name): static { if ($name !== null && (!Helpers::isIdentifier($name) || isset(Helpers::KEYWORDS[strtolower($name)]))) { throw new Nette\InvalidArgumentException("Value '$name' is not valid class name."); @@ -158,7 +151,7 @@ public function getName(): ?string /** @deprecated */ - public function setClass(): self + public function setClass(): static { $this->type = self::TYPE_CLASS; return $this; @@ -171,8 +164,7 @@ public function isClass(): bool } - /** @return static */ - public function setInterface(): self + public function setInterface(): static { $this->type = self::TYPE_INTERFACE; return $this; @@ -185,8 +177,7 @@ public function isInterface(): bool } - /** @return static */ - public function setTrait(): self + public function setTrait(): static { $this->type = self::TYPE_TRAIT; return $this; @@ -205,8 +196,7 @@ public function isEnum(): bool } - /** @return static */ - public function setType(string $type): self + public function setType(string $type): static { if (!in_array($type, [self::TYPE_CLASS, self::TYPE_INTERFACE, self::TYPE_TRAIT, self::TYPE_ENUM], true)) { throw new Nette\InvalidArgumentException('Argument must be class|interface|trait|enum.'); @@ -223,8 +213,7 @@ public function getType(): string } - /** @return static */ - public function setFinal(bool $state = true): self + public function setFinal(bool $state = true): static { $this->final = $state; return $this; @@ -237,8 +226,7 @@ public function isFinal(): bool } - /** @return static */ - public function setAbstract(bool $state = true): self + public function setAbstract(bool $state = true): static { $this->abstract = $state; return $this; @@ -253,14 +241,9 @@ public function isAbstract(): bool /** * @param string|string[] $names - * @return static */ - public function setExtends($names): self + public function setExtends(string|array $names): static { - if (!is_string($names) && !is_array($names)) { - throw new Nette\InvalidArgumentException('Argument must be string or string[].'); - } - $this->validateNames((array) $names); $this->extends = $names; return $this; @@ -268,14 +251,13 @@ public function setExtends($names): self /** @return string|string[] */ - public function getExtends() + public function getExtends(): string|array { return $this->extends; } - /** @return static */ - public function addExtend(string $name): self + public function addExtend(string $name): static { $this->validateNames([$name]); $this->extends = (array) $this->extends; @@ -286,9 +268,8 @@ public function addExtend(string $name): self /** * @param string[] $names - * @return static */ - public function setImplements(array $names): self + public function setImplements(array $names): static { $this->validateNames($names); $this->implements = $names; @@ -303,8 +284,7 @@ public function getImplements(): array } - /** @return static */ - public function addImplement(string $name): self + public function addImplement(string $name): static { $this->validateNames([$name]); $this->implements[] = $name; @@ -312,8 +292,7 @@ public function addImplement(string $name): self } - /** @return static */ - public function removeImplement(string $name): self + public function removeImplement(string $name): static { $this->implements = array_diff($this->implements, [$name]); return $this; @@ -322,10 +301,10 @@ public function removeImplement(string $name): self /** * @param string[]|TraitUse[] $traits - * @return static */ - public function setTraits(array $traits): self + public function setTraits(array $traits): static { + (function (TraitUse|string ...$traits) {})(...$traits); $this->traits = []; foreach ($traits as $trait) { if (!$trait instanceof TraitUse) { @@ -353,11 +332,7 @@ public function getTraitResolutions(): array } - /** - * @param array|bool $resolutions - * @return static|TraitUse - */ - public function addTrait(string $name, $resolutions = []) + public function addTrait(string $name, array|bool $resolutions = []): static|TraitUse { $this->traits[$name] = $trait = new TraitUse($name); if ($resolutions === true) { @@ -369,19 +344,14 @@ public function addTrait(string $name, $resolutions = []) } - /** @return static */ - public function removeTrait(string $name): self + public function removeTrait(string $name): static { unset($this->traits[$name]); return $this; } - /** - * @param Method|Property|Constant|EnumCase|TraitUse $member - * @return static - */ - public function addMember($member): self + public function addMember(Method|Property|Constant|EnumCase|TraitUse $member): static { match (true) { $member instanceof Method => $this->methods[strtolower($member->getName())] = $member, @@ -389,7 +359,6 @@ public function addMember($member): self $member instanceof Constant => $this->consts[$member->getName()] = $member, $member instanceof EnumCase => $this->cases[$member->getName()] = $member, $member instanceof TraitUse => $this->traits[$member->getName()] = $member, - default => throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant|EnumCase|TraitUse.'), }; return $this; } @@ -397,9 +366,8 @@ public function addMember($member): self /** * @param Constant[]|mixed[] $consts - * @return static */ - public function setConstants(array $consts): self + public function setConstants(array $consts): static { $this->consts = []; foreach ($consts as $k => $const) { @@ -429,8 +397,7 @@ public function addConstant(string $name, $value): Constant } - /** @return static */ - public function removeConstant(string $name): self + public function removeConstant(string $name): static { unset($this->consts[$name]); return $this; @@ -440,9 +407,8 @@ public function removeConstant(string $name): self /** * Sets cases to enum * @param EnumCase[] $cases - * @return static */ - public function setCases(array $cases): self + public function setCases(array $cases): static { (function (EnumCase ...$cases) {})(...array_values($cases)); $this->cases = []; @@ -462,15 +428,14 @@ public function getCases(): array /** Adds case to enum */ - public function addCase(string $name, $value = null): EnumCase + public function addCase(string $name, string|int|null $value = null): EnumCase { return $this->cases[$name] = (new EnumCase($name)) ->setValue($value); } - /** @return static */ - public function removeCase(string $name): self + public function removeCase(string $name): static { unset($this->cases[$name]); return $this; @@ -479,9 +444,8 @@ public function removeCase(string $name): self /** * @param Property[] $props - * @return static */ - public function setProperties(array $props): self + public function setProperties(array $props): static { (function (Property ...$props) {})(...array_values($props)); $this->properties = []; @@ -523,9 +487,8 @@ public function addProperty(string $name, $value = null): Property /** * @param string $name without $ - * @return static */ - public function removeProperty(string $name): self + public function removeProperty(string $name): static { unset($this->properties[$name]); return $this; @@ -540,9 +503,8 @@ public function hasProperty(string $name): bool /** * @param Method[] $methods - * @return static */ - public function setMethods(array $methods): self + public function setMethods(array $methods): static { (function (Method ...$methods) {})(...array_values($methods)); $this->methods = []; @@ -588,8 +550,7 @@ public function addMethod(string $name): Method } - /** @return static */ - public function removeMethod(string $name): self + public function removeMethod(string $name): static { unset($this->methods[strtolower($name)]); return $this; diff --git a/src/PhpGenerator/Closure.php b/src/PhpGenerator/Closure.php index 4c7e2023..7cc7a35c 100644 --- a/src/PhpGenerator/Closure.php +++ b/src/PhpGenerator/Closure.php @@ -41,9 +41,8 @@ public function __toString(): string /** * @param Parameter[] $uses - * @return static */ - public function setUses(array $uses): self + public function setUses(array $uses): static { (function (Parameter ...$uses) {})(...$uses); $this->uses = $uses; diff --git a/src/PhpGenerator/Constant.php b/src/PhpGenerator/Constant.php index 3c77ff70..b8ea0f4b 100644 --- a/src/PhpGenerator/Constant.php +++ b/src/PhpGenerator/Constant.php @@ -27,22 +27,20 @@ final class Constant private bool $final = false; - /** @return static */ - public function setValue($val): self + public function setValue($val): static { $this->value = $val; return $this; } - public function getValue() + public function getValue(): mixed { return $this->value; } - /** @return static */ - public function setFinal(bool $state = true): self + public function setFinal(bool $state = true): static { $this->final = $state; return $this; diff --git a/src/PhpGenerator/EnumCase.php b/src/PhpGenerator/EnumCase.php index 086c8711..193cc09c 100644 --- a/src/PhpGenerator/EnumCase.php +++ b/src/PhpGenerator/EnumCase.php @@ -25,8 +25,7 @@ final class EnumCase private string|int|null $value = null; - /** @return static */ - public function setValue($val): self + public function setValue(string|int|null $val): static { $this->value = $val; return $this; diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 59935436..17d4c957 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -347,7 +347,7 @@ private function addConstantToClass(ClassType $class, Node\Stmt\ClassConst $node } - private function addEnumCaseToClass(ClassType $class, Node\Stmt\EnumCase $node) + private function addEnumCaseToClass(ClassType $class, Node\Stmt\EnumCase $node): void { $case = $class->addCase($node->name->toString(), $node->expr?->value); $this->addCommentAndAttributes($case, $node); @@ -381,10 +381,7 @@ private function addCommentAndAttributes($element, Node $node): void } - /** - * @param GlobalFunction|Method $function - */ - private function setupFunction($function, Node\FunctionLike $node): void + private function setupFunction(GlobalFunction|Method $function, Node\FunctionLike $node): void { $function->setReturnReference($node->returnsByRef()); $function->setReturnType($node->getReturnType() ? $this->toPhp($node->getReturnType()) : null); @@ -407,7 +404,7 @@ private function setupFunction($function, Node\FunctionLike $node): void } - private function toPhp($value): string + private function toPhp(mixed $value): string { return $this->printer->prettyPrint([$value]); } diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index ad006331..e63d07fe 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -165,8 +165,7 @@ public function fromMethodReflection(\ReflectionMethod $from): Method } - /** @return GlobalFunction|Closure */ - public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody = false) + public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody = false): GlobalFunction|Closure { $function = $from->isClosure() ? new Closure : new GlobalFunction($from->name); $function->setParameters(array_map([$this, 'fromParameterReflection'], $from->getParameters())); @@ -199,8 +198,7 @@ public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody } - /** @return Method|GlobalFunction|Closure */ - public function fromCallable(callable $from) + public function fromCallable(callable $from): Method|GlobalFunction|Closure { $ref = Nette\Utils\Callback::toReflection($from); return $ref instanceof \ReflectionMethod @@ -316,7 +314,6 @@ public function fromCode(string $code): PhpFile private function getAttributes($from): array { - return array_map(function ($attr) { $args = $attr->getArguments(); foreach ($args as &$arg) { diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index 9725db63..30d0dd1e 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -31,10 +31,7 @@ final class Method private bool $abstract = false; - /** - * @param string|array $method - */ - public static function from($method): self + public static function from(string|array $method): static { return (new Factory)->fromMethodReflection(Nette\Utils\Callback::toReflection($method)); } @@ -46,8 +43,7 @@ public function __toString(): string } - /** @return static */ - public function setStatic(bool $state = true): self + public function setStatic(bool $state = true): static { $this->static = $state; return $this; @@ -60,8 +56,7 @@ public function isStatic(): bool } - /** @return static */ - public function setFinal(bool $state = true): self + public function setFinal(bool $state = true): static { $this->final = $state; return $this; @@ -74,8 +69,7 @@ public function isFinal(): bool } - /** @return static */ - public function setAbstract(bool $state = true): self + public function setAbstract(bool $state = true): static { $this->abstract = $state; return $this; diff --git a/src/PhpGenerator/Parameter.php b/src/PhpGenerator/Parameter.php index 478d336e..c5498771 100644 --- a/src/PhpGenerator/Parameter.php +++ b/src/PhpGenerator/Parameter.php @@ -31,8 +31,7 @@ class Parameter private mixed $defaultValue = null; - /** @return static */ - public function setReference(bool $state = true): self + public function setReference(bool $state = true): static { $this->reference = $state; return $this; @@ -45,18 +44,14 @@ public function isReference(): bool } - /** @return static */ - public function setType(?string $type): self + public function setType(?string $type): static { $this->type = Helpers::validateType($type, $this->nullable); return $this; } - /** - * @return Type|string|null - */ - public function getType(bool $asObject = false) + public function getType(bool $asObject = false): Type|string|null { return $asObject && $this->type ? Type::fromString($this->type) @@ -65,7 +60,7 @@ public function getType(bool $asObject = false) /** @deprecated use setType() */ - public function setTypeHint(?string $type): self + public function setTypeHint(?string $type): static { return $this->setType($type); } @@ -80,9 +75,8 @@ public function getTypeHint(): ?string /** * @deprecated just use setDefaultValue() - * @return static */ - public function setOptional(bool $state = true): self + public function setOptional(bool $state = true): static { trigger_error(__METHOD__ . '() is deprecated, use setDefaultValue()', E_USER_DEPRECATED); $this->hasDefaultValue = $state; @@ -90,8 +84,7 @@ public function setOptional(bool $state = true): self } - /** @return static */ - public function setNullable(bool $state = true): self + public function setNullable(bool $state = true): static { $this->nullable = $state; return $this; @@ -104,8 +97,7 @@ public function isNullable(): bool } - /** @return static */ - public function setDefaultValue($val): self + public function setDefaultValue(mixed $val): static { $this->defaultValue = $val; $this->hasDefaultValue = true; @@ -113,7 +105,7 @@ public function setDefaultValue($val): self } - public function getDefaultValue() + public function getDefaultValue(): mixed { return $this->defaultValue; } diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index e6db7ce1..98a4bf6e 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -68,18 +68,11 @@ public function addEnum(string $name): ClassType } - /** @param string|PhpNamespace $namespace */ - public function addNamespace($namespace): PhpNamespace + public function addNamespace(string|PhpNamespace $namespace): PhpNamespace { - if ($namespace instanceof PhpNamespace) { - $res = $this->namespaces[$namespace->getName()] = $namespace; - - } elseif (is_string($namespace)) { - $res = $this->namespaces[$namespace] ??= new PhpNamespace($namespace); - - } else { - throw new Nette\InvalidArgumentException('Argument must be string|PhpNamespace.'); - } + $res = $namespace instanceof PhpNamespace + ? ($this->namespaces[$namespace->getName()] = $namespace) + : ($this->namespaces[$namespace] ??= new PhpNamespace($namespace)); foreach ($this->namespaces as $namespace) { $namespace->setBracketedSyntax(count($this->namespaces) > 1 && isset($this->namespaces[''])); @@ -134,8 +127,7 @@ public function getFunctions(): array } - /** @return static */ - public function addUse(string $name, ?string $alias = null, string $of = PhpNamespace::NAME_NORMAL): self + public function addUse(string $name, ?string $alias = null, string $of = PhpNamespace::NAME_NORMAL): static { $this->addNamespace('')->addUse($name, $alias, $of); return $this; @@ -144,9 +136,8 @@ public function addUse(string $name, ?string $alias = null, string $of = PhpName /** * Adds declare(strict_types=1) to output. - * @return static */ - public function setStrictTypes(bool $on = true): self + public function setStrictTypes(bool $on = true): static { $this->strictTypes = $on; return $this; diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 80ea024d..c2531598 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -65,10 +65,9 @@ public function getName(): string /** - * @return static * @internal */ - public function setBracketedSyntax(bool $state = true): self + public function setBracketedSyntax(bool $state = true): static { $this->bracketedSyntax = $state; return $this; @@ -90,9 +89,8 @@ public function getBracketedSyntax(): bool /** * @throws InvalidStateException - * @return static */ - public function addUse(string $name, ?string $alias = null, string $of = self::NAME_NORMAL): self + public function addUse(string $name, ?string $alias = null, string $of = self::NAME_NORMAL): static { if ( !Helpers::isNamespaceIdentifier($name, true) @@ -132,15 +130,13 @@ public function addUse(string $name, ?string $alias = null, string $of = self::N } - /** @return static */ - public function addUseFunction(string $name, ?string $alias = null): self + public function addUseFunction(string $name, ?string $alias = null): static { return $this->addUse($name, $alias, self::NAME_FUNCTION); } - /** @return static */ - public function addUseConstant(string $name, ?string $alias = null): self + public function addUseConstant(string $name, ?string $alias = null): static { return $this->addUse($name, $alias, self::NAME_CONSTANT); } @@ -236,8 +232,7 @@ public function simplifyName(string $name, string $of = self::NAME_NORMAL): stri } - /** @return static */ - public function add(ClassType $class): self + public function add(ClassType $class): static { $name = $class->getName(); if ($name === null) { @@ -279,7 +274,7 @@ public function addEnum(string $name): ClassType } - public function removeClass(string $name): self + public function removeClass(string $name): static { unset($this->classes[strtolower($name)]); return $this; @@ -297,7 +292,7 @@ public function addFunction(string $name): GlobalFunction } - public function removeFunction(string $name): self + public function removeFunction(string $name): static { unset($this->functions[strtolower($name)]); return $this; diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 27800e7f..d626fa47 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -286,10 +286,7 @@ protected function printUses(PhpNamespace $namespace, string $of = PhpNamespace: } - /** - * @param Closure|GlobalFunction|Method $function - */ - protected function printParameters($function, int $column = 0): string + protected function printParameters(Closure|GlobalFunction|Method $function, int $column = 0): string { $params = []; $list = $function->getParameters(); @@ -344,10 +341,7 @@ protected function printType(?string $type, bool $nullable): string } - /** - * @param Closure|GlobalFunction|Method $function - */ - private function printReturnType($function): string + private function printReturnType(Closure|GlobalFunction|Method $function): string { return ($tmp = $this->printType($function->getReturnType(), $function->isReturnNullable())) ? $this->returnTypeColon . $tmp @@ -375,8 +369,7 @@ private function printAttributes(array $attrs, bool $inline = false): string } - /** @return static */ - public function setTypeResolving(bool $state = true): self + public function setTypeResolving(bool $state = true): static { $this->resolveTypes = $state; return $this; diff --git a/src/PhpGenerator/PromotedParameter.php b/src/PhpGenerator/PromotedParameter.php index 57e7f28c..65851db2 100644 --- a/src/PhpGenerator/PromotedParameter.php +++ b/src/PhpGenerator/PromotedParameter.php @@ -23,8 +23,7 @@ final class PromotedParameter extends Parameter private bool $readOnly = false; - /** @return static */ - public function setReadOnly(bool $state = true): self + public function setReadOnly(bool $state = true): static { $this->readOnly = $state; return $this; diff --git a/src/PhpGenerator/Property.php b/src/PhpGenerator/Property.php index e05a8d16..34c0605b 100644 --- a/src/PhpGenerator/Property.php +++ b/src/PhpGenerator/Property.php @@ -34,8 +34,7 @@ final class Property private bool $readOnly = false; - /** @return static */ - public function setValue($val): self + public function setValue(mixed $val): static { $this->value = $val; $this->initialized = true; @@ -43,14 +42,13 @@ public function setValue($val): self } - public function &getValue() + public function &getValue(): mixed { return $this->value; } - /** @return static */ - public function setStatic(bool $state = true): self + public function setStatic(bool $state = true): static { $this->static = $state; return $this; @@ -63,18 +61,14 @@ public function isStatic(): bool } - /** @return static */ - public function setType(?string $type): self + public function setType(?string $type): static { $this->type = Helpers::validateType($type, $this->nullable); return $this; } - /** - * @return Type|string|null - */ - public function getType(bool $asObject = false) + public function getType(bool $asObject = false): Type|string|null { return $asObject && $this->type ? Type::fromString($this->type) @@ -82,8 +76,7 @@ public function getType(bool $asObject = false) } - /** @return static */ - public function setNullable(bool $state = true): self + public function setNullable(bool $state = true): static { $this->nullable = $state; return $this; @@ -96,8 +89,7 @@ public function isNullable(): bool } - /** @return static */ - public function setInitialized(bool $state = true): self + public function setInitialized(bool $state = true): static { $this->initialized = $state; return $this; @@ -110,8 +102,7 @@ public function isInitialized(): bool } - /** @return static */ - public function setReadOnly(bool $state = true): self + public function setReadOnly(bool $state = true): static { $this->readOnly = $state; return $this; diff --git a/src/PhpGenerator/TraitUse.php b/src/PhpGenerator/TraitUse.php index 72bb87e4..caae9271 100644 --- a/src/PhpGenerator/TraitUse.php +++ b/src/PhpGenerator/TraitUse.php @@ -34,7 +34,7 @@ public function __construct(string $name) } - public function addResolution(string $resolution): self + public function addResolution(string $resolution): static { $this->resolutions[] = $resolution; return $this; diff --git a/src/PhpGenerator/Traits/AttributeAware.php b/src/PhpGenerator/Traits/AttributeAware.php index 00ca0834..56650451 100644 --- a/src/PhpGenerator/Traits/AttributeAware.php +++ b/src/PhpGenerator/Traits/AttributeAware.php @@ -21,8 +21,7 @@ trait AttributeAware private array $attributes = []; - /** @return static */ - public function addAttribute(string $name, array $args = []): self + public function addAttribute(string $name, array $args = []): static { $this->attributes[] = new Attribute($name, $args); return $this; @@ -31,9 +30,8 @@ public function addAttribute(string $name, array $args = []): self /** * @param Attribute[] $attrs - * @return static */ - public function setAttributes(array $attrs): self + public function setAttributes(array $attrs): static { (function (Attribute ...$attrs) {})(...$attrs); $this->attributes = $attrs; diff --git a/src/PhpGenerator/Traits/CommentAware.php b/src/PhpGenerator/Traits/CommentAware.php index 2acc5062..85d380b2 100644 --- a/src/PhpGenerator/Traits/CommentAware.php +++ b/src/PhpGenerator/Traits/CommentAware.php @@ -18,8 +18,7 @@ trait CommentAware private ?string $comment = null; - /** @return static */ - public function setComment(?string $val): self + public function setComment(?string $val): static { $this->comment = $val; return $this; @@ -32,8 +31,7 @@ public function getComment(): ?string } - /** @return static */ - public function addComment(string $val): self + public function addComment(string $val): static { $this->comment .= $this->comment ? "\n$val" : $val; return $this; diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index 5213399c..acf9913a 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -30,8 +30,7 @@ trait FunctionLike private bool $returnNullable = false; - /** @return static */ - public function setBody(string $code, ?array $args = null): self + public function setBody(string $code, ?array $args = null): static { $this->body = $args === null ? $code @@ -46,8 +45,7 @@ public function getBody(): string } - /** @return static */ - public function addBody(string $code, ?array $args = null): self + public function addBody(string $code, ?array $args = null): static { $this->body .= ($args === null ? $code : (new Dumper)->format($code, ...$args)) . "\n"; return $this; @@ -56,9 +54,8 @@ public function addBody(string $code, ?array $args = null): self /** * @param Parameter[] $val - * @return static */ - public function setParameters(array $val): self + public function setParameters(array $val): static { (function (Parameter ...$val) {})(...array_values($val)); $this->parameters = []; @@ -93,17 +90,15 @@ public function addParameter(string $name, $defaultValue = null): Parameter /** * @param string $name without $ - * @return static */ - public function removeParameter(string $name): self + public function removeParameter(string $name): static { unset($this->parameters[$name]); return $this; } - /** @return static */ - public function setVariadic(bool $state = true): self + public function setVariadic(bool $state = true): static { $this->variadic = $state; return $this; @@ -116,18 +111,14 @@ public function isVariadic(): bool } - /** @return static */ - public function setReturnType(?string $type): self + public function setReturnType(?string $type): static { $this->returnType = Nette\PhpGenerator\Helpers::validateType($type, $this->returnNullable); return $this; } - /** - * @return Type|string|null - */ - public function getReturnType(bool $asObject = false) + public function getReturnType(bool $asObject = false): Type|string|null { return $asObject && $this->returnType ? Type::fromString($this->returnType) @@ -135,8 +126,7 @@ public function getReturnType(bool $asObject = false) } - /** @return static */ - public function setReturnReference(bool $state = true): self + public function setReturnReference(bool $state = true): static { $this->returnReference = $state; return $this; @@ -149,8 +139,7 @@ public function getReturnReference(): bool } - /** @return static */ - public function setReturnNullable(bool $state = true): self + public function setReturnNullable(bool $state = true): static { $this->returnNullable = $state; return $this; @@ -171,7 +160,7 @@ public function getReturnNullable(): bool /** @deprecated */ - public function setNamespace(?Nette\PhpGenerator\PhpNamespace $val = null): self + public function setNamespace(?Nette\PhpGenerator\PhpNamespace $val = null): static { trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED); return $this; diff --git a/src/PhpGenerator/Traits/NameAware.php b/src/PhpGenerator/Traits/NameAware.php index 7bac08d6..16de3563 100644 --- a/src/PhpGenerator/Traits/NameAware.php +++ b/src/PhpGenerator/Traits/NameAware.php @@ -38,9 +38,8 @@ public function getName(): string /** * Returns clone with a different name. - * @return static */ - public function cloneWithName(string $name): self + public function cloneWithName(string $name): static { $dolly = clone $this; $dolly->__construct($name); diff --git a/src/PhpGenerator/Traits/VisibilityAware.php b/src/PhpGenerator/Traits/VisibilityAware.php index 3b5c676a..5831efeb 100644 --- a/src/PhpGenerator/Traits/VisibilityAware.php +++ b/src/PhpGenerator/Traits/VisibilityAware.php @@ -24,9 +24,8 @@ trait VisibilityAware /** * @param string|null $val public|protected|private - * @return static */ - public function setVisibility(?string $val): self + public function setVisibility(?string $val): static { if (!in_array($val, [ClassType::VISIBILITY_PUBLIC, ClassType::VISIBILITY_PROTECTED, ClassType::VISIBILITY_PRIVATE, null], true)) { throw new Nette\InvalidArgumentException('Argument must be public|protected|private.'); @@ -43,8 +42,7 @@ public function getVisibility(): ?string } - /** @return static */ - public function setPublic(): self + public function setPublic(): static { $this->visibility = ClassType::VISIBILITY_PUBLIC; return $this; @@ -57,8 +55,7 @@ public function isPublic(): bool } - /** @return static */ - public function setProtected(): self + public function setProtected(): static { $this->visibility = ClassType::VISIBILITY_PROTECTED; return $this; @@ -71,8 +68,7 @@ public function isProtected(): bool } - /** @return static */ - public function setPrivate(): self + public function setPrivate(): static { $this->visibility = ClassType::VISIBILITY_PRIVATE; return $this; diff --git a/tests/PhpGenerator/ClassType.addMember.phpt b/tests/PhpGenerator/ClassType.addMember.phpt index 6a1ba458..7d25fa3c 100644 --- a/tests/PhpGenerator/ClassType.addMember.phpt +++ b/tests/PhpGenerator/ClassType.addMember.phpt @@ -9,12 +9,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -Assert::exception(function () { - (new ClassType('Example')) - ->addMember(new stdClass); -}, Nette\InvalidArgumentException::class, 'Argument must be Method|Property|Constant|EnumCase|TraitUse.'); - - $class = (new ClassType('Example')) ->addMember($method = new Nette\PhpGenerator\Method('GETHANDLE')) ->addMember($method = new Nette\PhpGenerator\Method('getHandle')) From 9a2d6a8f4ee02b7fb33f2ddbb44c2e8cda699bd2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 18 Sep 2021 17:53:45 +0200 Subject: [PATCH 097/266] used native PHP 8 functions --- src/PhpGenerator/ClassType.php | 6 +++--- src/PhpGenerator/Dumper.php | 4 ++-- src/PhpGenerator/Extractor.php | 2 +- src/PhpGenerator/Helpers.php | 6 +++--- src/PhpGenerator/Printer.php | 10 +++++----- src/PhpGenerator/Traits/FunctionLike.php | 2 +- src/PhpGenerator/Type.php | 16 +--------------- 7 files changed, 16 insertions(+), 30 deletions(-) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index bc47f040..f2e621d7 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -410,7 +410,7 @@ public function removeConstant(string $name): static */ public function setCases(array $cases): static { - (function (EnumCase ...$cases) {})(...array_values($cases)); + (function (EnumCase ...$cases) {})(...$cases); $this->cases = []; foreach ($cases as $case) { $this->cases[$case->getName()] = $case; @@ -447,7 +447,7 @@ public function removeCase(string $name): static */ public function setProperties(array $props): static { - (function (Property ...$props) {})(...array_values($props)); + (function (Property ...$props) {})(...$props); $this->properties = []; foreach ($props as $v) { $this->properties[$v->getName()] = $v; @@ -506,7 +506,7 @@ public function hasProperty(string $name): bool */ public function setMethods(array $methods): static { - (function (Method ...$methods) {})(...array_values($methods)); + (function (Method ...$methods) {})(...$methods); $this->methods = []; foreach ($methods as $m) { $this->methods[strtolower($m->getName())] = $m; diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 32de7fd0..4fd6d845 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -125,7 +125,7 @@ private function dumpArray(array &$var, array $parents, int $level, int $column) } array_pop($parents); - $wrap = strpos($outInline, "\n") !== false || $level * self::INDENT_LENGTH + $column + strlen($outInline) + 3 > $this->wrapLength; // 3 = [], + $wrap = str_contains($outInline, "\n") || $level * self::INDENT_LENGTH + $column + strlen($outInline) + 3 > $this->wrapLength; // 3 = [], return '[' . ($wrap ? $outWrapped : $outInline) . ']'; } @@ -251,7 +251,7 @@ private function dumpArguments(array &$var, int $column, bool $named): string . $this->indentation . $k . $this->dumpVar($v, [$var], 1); } - return count($var) > 1 && (strpos($outInline, "\n") !== false || $column + strlen($outInline) > $this->wrapLength) + return count($var) > 1 && (str_contains($outInline, "\n") || $column + strlen($outInline) > $this->wrapLength) ? $outWrapped . "\n" : $outInline; } diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 17d4c957..17b8f6ab 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -42,7 +42,7 @@ public function __construct(string $code) private function parseCode(string $code): void { - if (substr($code, 0, 5) !== 'isAbstract() || $isInterface ? ";\n" - : (strpos($params, "\n") === false ? "\n" : ' ') + : (str_contains($params, "\n") ? ' ' : "\n") . "{\n" . $this->indent(ltrim(rtrim($body) . "\n")) . "}\n"); @@ -154,7 +154,7 @@ public function printClass(ClassType $class, ?PhpNamespace $namespace = null): s } $enumType = isset($case) && $case->getValue() !== null - ? $this->returnTypeColon . Type::getType($case->getValue()) + ? $this->returnTypeColon . get_debug_type($case->getValue()) : ''; $consts = []; @@ -332,9 +332,9 @@ protected function printType(?string $type, bool $nullable): string } if ($nullable && strcasecmp($type, 'mixed')) { - $type = strpos($type, '|') === false - ? '?' . $type - : $type . '|null'; + $type = str_contains($type, '|') + ? $type . '|null' + : '?' . $type; } return $type; diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index acf9913a..9f3d2561 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -57,7 +57,7 @@ public function addBody(string $code, ?array $args = null): static */ public function setParameters(array $val): static { - (function (Parameter ...$val) {})(...array_values($val)); + (function (Parameter ...$val) {})(...$val); $this->parameters = []; foreach ($val as $v) { $this->parameters[$v->getName()] = $v; diff --git a/src/PhpGenerator/Type.php b/src/PhpGenerator/Type.php index 9a30ac6c..734bb737 100644 --- a/src/PhpGenerator/Type.php +++ b/src/PhpGenerator/Type.php @@ -54,20 +54,6 @@ public static function intersection(string ...$types): string public static function getType($value): ?string { - if (is_object($value)) { - return $value::class; - } elseif (is_int($value)) { - return self::INT; - } elseif (is_float($value)) { - return self::FLOAT; - } elseif (is_string($value)) { - return self::STRING; - } elseif (is_bool($value)) { - return self::BOOL; - } elseif (is_array($value)) { - return self::ARRAY; - } else { - return null; - } + return is_resource($value) ? null : get_debug_type($value); } } From fc77854c34961aad66070b356474cc6b5f6f5b29 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 2 Mar 2021 15:01:08 +0100 Subject: [PATCH 098/266] removed deprecated stuff --- src/PhpGenerator/Parameter.php | 11 ----------- src/PhpGenerator/Traits/FunctionLike.php | 8 -------- 2 files changed, 19 deletions(-) diff --git a/src/PhpGenerator/Parameter.php b/src/PhpGenerator/Parameter.php index c5498771..5fef1e69 100644 --- a/src/PhpGenerator/Parameter.php +++ b/src/PhpGenerator/Parameter.php @@ -73,17 +73,6 @@ public function getTypeHint(): ?string } - /** - * @deprecated just use setDefaultValue() - */ - public function setOptional(bool $state = true): static - { - trigger_error(__METHOD__ . '() is deprecated, use setDefaultValue()', E_USER_DEPRECATED); - $this->hasDefaultValue = $state; - return $this; - } - - public function setNullable(bool $state = true): static { $this->nullable = $state; diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index 9f3d2561..affb40cd 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -157,12 +157,4 @@ public function getReturnNullable(): bool { return $this->returnNullable; } - - - /** @deprecated */ - public function setNamespace(?Nette\PhpGenerator\PhpNamespace $val = null): static - { - trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED); - return $this; - } } From c1d720e1762e2646bf221087dbc5881bc467a21d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 15 Sep 2021 17:05:34 +0200 Subject: [PATCH 099/266] marked new deprecated stuff --- composer.json | 2 +- readme.md | 4 ++-- src/PhpGenerator/ClassType.php | 9 ++++++++- src/PhpGenerator/GlobalFunction.php | 2 ++ src/PhpGenerator/Helpers.php | 3 +++ src/PhpGenerator/Parameter.php | 2 ++ src/PhpGenerator/PhpFile.php | 1 + src/PhpGenerator/PhpLiteral.php | 1 + src/PhpGenerator/PhpNamespace.php | 2 ++ src/PhpGenerator/Traits/FunctionLike.php | 1 + src/PhpGenerator/Type.php | 2 ++ tests/PhpGenerator/ClassType.from.bodies.phpt | 8 ++++---- tests/PhpGenerator/ClassType.from.trait.phpt | 2 +- tests/PhpGenerator/GlobalFunction.phpt | 2 +- tests/PhpGenerator/Type.phpt | 7 ------- 15 files changed, 31 insertions(+), 17 deletions(-) diff --git a/composer.json b/composer.json index 3c5dbf23..71358c30 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "phpstan/phpstan": "^1.0" }, "suggest": { - "nikic/php-parser": "to use ClassType::withBodiesFrom() & GlobalFunction::withBodyFrom()" + "nikic/php-parser": "to use ClassType::from(withBodies: true) & ClassType::fromCode()" }, "autoload": { "classmap": ["src/"] diff --git a/readme.md b/readme.md index c05c463d..30923a6e 100644 --- a/readme.md +++ b/readme.md @@ -721,9 +721,9 @@ Function and method bodies are empty by default. If you want to load them as wel (it requires `nikic/php-parser` to be installed): ```php -$class = Nette\PhpGenerator\ClassType::withBodiesFrom(MyClass::class); +$class = Nette\PhpGenerator\ClassType::from(PDO::class, withBodies: true); -$function = Nette\PhpGenerator\GlobalFunction::withBodyFrom('dump'); +$function = Nette\PhpGenerator\GlobalFunction::from('dump', withBody: true); ``` Load class from file diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index f2e621d7..bbd409f6 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -99,8 +99,10 @@ public static function from(string|object $class, bool $withBodies = false, bool } + /** @deprecated use ClassType::from(..., withBodies: true) */ public static function withBodiesFrom(string|object $class): self { + trigger_error(__METHOD__ . '() is deprecated, use ClassType::from(..., withBodies: true)', E_USER_DEPRECATED); return (new Factory) ->fromClassReflection(new \ReflectionClass($class), withBodies: true); } @@ -150,9 +152,10 @@ public function getName(): ?string } - /** @deprecated */ + /** @deprecated use setType('class') or create using ClassType::class() */ public function setClass(): static { + trigger_error(__METHOD__ . "() is deprecated, use setType('class').", E_USER_DEPRECATED); $this->type = self::TYPE_CLASS; return $this; } @@ -164,8 +167,10 @@ public function isClass(): bool } + /** @deprecated use setType('interface') or create using ClassType::interface() */ public function setInterface(): static { + trigger_error(__METHOD__ . '() is deprecated, use $class->setType($class::TYPE_INTERFACE) or create object using ClassType::interface()', E_USER_DEPRECATED); $this->type = self::TYPE_INTERFACE; return $this; } @@ -177,8 +182,10 @@ public function isInterface(): bool } + /** @deprecated use setType('trait') or create using ClassType::trait() */ public function setTrait(): static { + trigger_error(__METHOD__ . '() is deprecated, use $class->setType($class::TYPE_TRAIT) or create object using ClassType::trait()', E_USER_DEPRECATED); $this->type = self::TYPE_TRAIT; return $this; } diff --git a/src/PhpGenerator/GlobalFunction.php b/src/PhpGenerator/GlobalFunction.php index f1c0012b..8d55a393 100644 --- a/src/PhpGenerator/GlobalFunction.php +++ b/src/PhpGenerator/GlobalFunction.php @@ -31,8 +31,10 @@ public static function from(string $function, bool $withBody = false): self } + /** @deprecated use GlobalFunction::from(..., withBody: true) */ public static function withBodyFrom(string $function): self { + trigger_error(__METHOD__ . '() is deprecated, use GlobalFunction::from(..., withBody: true)', E_USER_DEPRECATED); return (new Factory)->fromFunctionReflection(new \ReflectionFunction($function), withBody: true); } diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index edf9c7b7..8d835b8a 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -51,6 +51,7 @@ final class Helpers /** @deprecated use (new Nette\PhpGenerator\Dumper)->dump() */ public static function dump($var): string { + trigger_error(__METHOD__ . '() is deprecated, use (new Nette\PhpGenerator\Dumper)->dump().', E_USER_DEPRECATED); return (new Dumper)->dump($var); } @@ -58,6 +59,7 @@ public static function dump($var): string /** @deprecated use (new Nette\PhpGenerator\Dumper)->format() */ public static function format(string $statement, ...$args): string { + trigger_error(__METHOD__ . '() is deprecated, use (new Nette\PhpGenerator\Dumper)->format().', E_USER_DEPRECATED); return (new Dumper)->format($statement, ...$args); } @@ -65,6 +67,7 @@ public static function format(string $statement, ...$args): string /** @deprecated use (new Nette\PhpGenerator\Dumper)->format() */ public static function formatArgs(string $statement, array $args): string { + trigger_error(__METHOD__ . '() is deprecated, use (new Nette\PhpGenerator\Dumper)->format().', E_USER_DEPRECATED); return (new Dumper)->format($statement, ...$args); } diff --git a/src/PhpGenerator/Parameter.php b/src/PhpGenerator/Parameter.php index 5fef1e69..11c4c4ee 100644 --- a/src/PhpGenerator/Parameter.php +++ b/src/PhpGenerator/Parameter.php @@ -62,6 +62,7 @@ public function getType(bool $asObject = false): Type|string|null /** @deprecated use setType() */ public function setTypeHint(?string $type): static { + trigger_error(__METHOD__ . '() is deprecated, use setType().', E_USER_DEPRECATED); return $this->setType($type); } @@ -69,6 +70,7 @@ public function setTypeHint(?string $type): static /** @deprecated use getType() */ public function getTypeHint(): ?string { + trigger_error(__METHOD__ . '() is deprecated, use getType().', E_USER_DEPRECATED); return $this->getType(); } diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index 98a4bf6e..6f7270f2 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -153,6 +153,7 @@ public function hasStrictTypes(): bool /** @deprecated use hasStrictTypes() */ public function getStrictTypes(): bool { + trigger_error(__METHOD__ . '() is deprecated, use hasStrictTypes().', E_USER_DEPRECATED); return $this->strictTypes; } diff --git a/src/PhpGenerator/PhpLiteral.php b/src/PhpGenerator/PhpLiteral.php index 10fd5a56..460c5c4e 100644 --- a/src/PhpGenerator/PhpLiteral.php +++ b/src/PhpGenerator/PhpLiteral.php @@ -10,6 +10,7 @@ namespace Nette\PhpGenerator; +/** @deprecated use Nette\PhpGenerator\Literal */ class PhpLiteral extends Literal { } diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index c2531598..143024ff 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -83,6 +83,7 @@ public function hasBracketedSyntax(): bool /** @deprecated use hasBracketedSyntax() */ public function getBracketedSyntax(): bool { + trigger_error(__METHOD__ . '() is deprecated, use hasBracketedSyntax().', E_USER_DEPRECATED); return $this->bracketedSyntax; } @@ -157,6 +158,7 @@ public function getUses(string $of = self::NAME_NORMAL): array /** @deprecated use simplifyName() */ public function unresolveName(string $name): string { + trigger_error(__METHOD__ . '() is deprecated, use simplifyName()', E_USER_DEPRECATED); return $this->simplifyName($name); } diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index affb40cd..367a84f9 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -155,6 +155,7 @@ public function isReturnNullable(): bool /** @deprecated use isReturnNullable() */ public function getReturnNullable(): bool { + trigger_error(__METHOD__ . '() is deprecated, use isReturnNullable().', E_USER_DEPRECATED); return $this->returnNullable; } } diff --git a/src/PhpGenerator/Type.php b/src/PhpGenerator/Type.php index 734bb737..b12cf3c1 100644 --- a/src/PhpGenerator/Type.php +++ b/src/PhpGenerator/Type.php @@ -52,8 +52,10 @@ public static function intersection(string ...$types): string } + /** @deprecated use get_debug_type() */ public static function getType($value): ?string { + trigger_error(__METHOD__ . '() is deprecated, use PHP function get_debug_type()', E_USER_DEPRECATED); return is_resource($value) ? null : get_debug_type($value); } } diff --git a/tests/PhpGenerator/ClassType.from.bodies.phpt b/tests/PhpGenerator/ClassType.from.bodies.phpt index 0151bcc9..7e8258c8 100644 --- a/tests/PhpGenerator/ClassType.from.bodies.phpt +++ b/tests/PhpGenerator/ClassType.from.bodies.phpt @@ -11,18 +11,18 @@ require __DIR__ . '/fixtures/bodies.php'; Assert::exception(function () { - ClassType::withBodiesFrom(PDO::class); + ClassType::from(PDO::class, withBodies: true); }, Nette\InvalidStateException::class, 'Source code of PDO not found.'); Assert::exception(function () { - ClassType::withBodiesFrom(new class { + ClassType::from(new class { public function f() { } - }); + }, withBodies: true); }, Nette\NotSupportedException::class, 'The $withBodies parameter cannot be used for anonymous functions.'); -$res = ClassType::withBodiesFrom(Abc\Class7::class); +$res = ClassType::from(Abc\Class7::class, withBodies: true); sameFile(__DIR__ . '/expected/ClassType.from.bodies.expect', (string) $res); diff --git a/tests/PhpGenerator/ClassType.from.trait.phpt b/tests/PhpGenerator/ClassType.from.trait.phpt index 2b596f35..6ea572c3 100644 --- a/tests/PhpGenerator/ClassType.from.trait.phpt +++ b/tests/PhpGenerator/ClassType.from.trait.phpt @@ -25,7 +25,7 @@ $res = array_map(fn($class) => ClassType::from($class), $classes); sameFile(__DIR__ . '/expected/ClassType.from.trait-materialize.expect', implode("\n", $res)); -$res = array_map(fn($class) => ClassType::withBodiesFrom($class), $classes); +$res = array_map(fn($class) => ClassType::from($class, withBodies: true), $classes); sameFile(__DIR__ . '/expected/ClassType.from.trait-materialize.bodies.expect', implode("\n", $res)); diff --git a/tests/PhpGenerator/GlobalFunction.phpt b/tests/PhpGenerator/GlobalFunction.phpt index fa435f94..2fbd2858 100644 --- a/tests/PhpGenerator/GlobalFunction.phpt +++ b/tests/PhpGenerator/GlobalFunction.phpt @@ -48,7 +48,7 @@ function func(stdClass $a, $b = null) ); -$function = GlobalFunction::withBodyFrom('func'); +$function = GlobalFunction::from('func', withBody: true); same(<<<'XX' /** * global diff --git a/tests/PhpGenerator/Type.phpt b/tests/PhpGenerator/Type.phpt index d487f18b..84626f77 100644 --- a/tests/PhpGenerator/Type.phpt +++ b/tests/PhpGenerator/Type.phpt @@ -16,10 +16,3 @@ Assert::same('A', Type::nullable(A::class, false)); Assert::same('?A', Type::nullable('?A', true)); Assert::same('A', Type::nullable('?A', false)); - -Assert::same(stdClass::class, Type::getType(new stdClass)); -Assert::same(Type::STRING, Type::getType('')); -Assert::same(Type::INT, Type::getType(1)); -Assert::same(Type::FLOAT, Type::getType(1.0)); -Assert::same(Type::ARRAY, Type::getType([])); -Assert::same(null, Type::getType(fopen(__FILE__, 'r'))); From a50da77ee0ece6a0fb235aa93cf5d02bed8a7e83 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 15 Sep 2021 13:26:54 +0200 Subject: [PATCH 100/266] Dumper: prints comma after last argument --- src/PhpGenerator/Dumper.php | 3 +-- tests/PhpGenerator/Dumper.format().phpt | 2 +- tests/PhpGenerator/Dumper.format().wrap.phpt | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 4fd6d845..24e7b62e 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -247,8 +247,7 @@ private function dumpArguments(array &$var, int $column, bool $named): string $k = !$named || is_int($k) ? '' : $k . ': '; $outInline .= $outInline === '' ? '' : ', '; $outInline .= $k . $this->dumpVar($v, [$var], 0, $column + strlen($outInline)); - $outWrapped .= ($outWrapped === '' ? '' : ',') . "\n" - . $this->indentation . $k . $this->dumpVar($v, [$var], 1); + $outWrapped .= "\n" . $this->indentation . $k . $this->dumpVar($v, [$var], 1) . ','; } return count($var) > 1 && (str_contains($outInline, "\n") || $column + strlen($outInline) > $this->wrapLength) diff --git a/tests/PhpGenerator/Dumper.format().phpt b/tests/PhpGenerator/Dumper.format().phpt index da1e31a2..743864de 100644 --- a/tests/PhpGenerator/Dumper.format().phpt +++ b/tests/PhpGenerator/Dumper.format().phpt @@ -54,7 +54,7 @@ same( 33, 34, 35, - 36 + 36, )', $dumper->format('func(?*)', range(10, 36)), ); diff --git a/tests/PhpGenerator/Dumper.format().wrap.phpt b/tests/PhpGenerator/Dumper.format().wrap.phpt index 896e783f..f5ef45ea 100644 --- a/tests/PhpGenerator/Dumper.format().wrap.phpt +++ b/tests/PhpGenerator/Dumper.format().wrap.phpt @@ -39,12 +39,12 @@ Assert::same('func(1, 2, 3)', $dumper->format('func(...?)', [1, 2, 3])); same('loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong( 1, 2, - 3 + 3, )', $dumper->format('loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong(...?)', [1, 2, 3])); same('looooooooooooooooooooooooooooooooooooooooo(1, 2, 3) + ooooooooooooooooooooooooooooooooooooooooooooooong( 1, 2, - 3 + 3, )', $dumper->format('looooooooooooooooooooooooooooooooooooooooo(...?) + ooooooooooooooooooooooooooooooooooooooooooooooong(...?)', [1, 2, 3], [1, 2, 3])); From 416a8736c6b93ed0131c8946a36ee6a289e5d221 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 22 Sep 2021 10:35:55 +0200 Subject: [PATCH 101/266] refactoring --- src/PhpGenerator/Factory.php | 40 ++++-------------------------------- 1 file changed, 4 insertions(+), 36 deletions(-) diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index e63d07fe..7ff49f06 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -151,15 +151,7 @@ public function fromMethodReflection(\ReflectionMethod $from): Method $method->setVariadic($from->isVariadic()); $method->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); $method->setAttributes(self::getAttributes($from)); - if ($from->getReturnType() instanceof \ReflectionNamedType) { - $method->setReturnType($from->getReturnType()->getName()); - $method->setReturnNullable($from->getReturnType()->allowsNull()); - } elseif ( - $from->getReturnType() instanceof \ReflectionUnionType - || $from->getReturnType() instanceof \ReflectionIntersectionType - ) { - $method->setReturnType((string) $from->getReturnType()); - } + $method->setReturnType((string) $from->getReturnType()); return $method; } @@ -176,15 +168,7 @@ public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody } $function->setAttributes(self::getAttributes($from)); - if ($from->getReturnType() instanceof \ReflectionNamedType) { - $function->setReturnType($from->getReturnType()->getName()); - $function->setReturnNullable($from->getReturnType()->allowsNull()); - } elseif ( - $from->getReturnType() instanceof \ReflectionUnionType - || $from->getReturnType() instanceof \ReflectionIntersectionType - ) { - $function->setReturnType((string) $from->getReturnType()); - } + $function->setReturnType((string) $from->getReturnType()); if ($withBody) { if ($from->isClosure()) { @@ -213,15 +197,7 @@ public function fromParameterReflection(\ReflectionParameter $from): Parameter ? new PromotedParameter($from->name) : new Parameter($from->name); $param->setReference($from->isPassedByReference()); - if ($from->getType() instanceof \ReflectionNamedType) { - $param->setType($from->getType()->getName()); - $param->setNullable($from->getType()->allowsNull()); - } elseif ( - $from->getType() instanceof \ReflectionUnionType - || $from->getType() instanceof \ReflectionIntersectionType - ) { - $param->setType((string) $from->getType()); - } + $param->setType((string) $from->getType()); if ($from->isDefaultValueAvailable()) { if ($from->isDefaultValueConstant()) { @@ -274,15 +250,7 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property $prop->setValue($defaults[$prop->getName()] ?? null); $prop->setStatic($from->isStatic()); $prop->setVisibility($this->getVisibility($from)); - if ($from->getType() instanceof \ReflectionNamedType) { - $prop->setType($from->getType()->getName()); - $prop->setNullable($from->getType()->allowsNull()); - } elseif ( - $from->getType() instanceof \ReflectionUnionType - || $from->getType() instanceof \ReflectionIntersectionType - ) { - $prop->setType((string) $from->getType()); - } + $prop->setType((string) $from->getType()); $prop->setInitialized($from->hasType() && array_key_exists($prop->getName(), $defaults)); $prop->setReadOnly(PHP_VERSION_ID >= 80100 ? $from->isReadOnly() : false); From 0d03544129db80661e063688b54e94e51a0701ec Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 22 Sep 2021 10:53:30 +0200 Subject: [PATCH 102/266] ClassType::setConstants() and setTraits() accepts array of Constant resp TraitUse only (BC break) --- src/PhpGenerator/ClassType.php | 6 ++++-- tests/PhpGenerator/ClassType.enum.phpt | 10 +++++----- tests/PhpGenerator/ClassType.phpt | 14 ++++++++------ tests/PhpGenerator/Printer.namespace.phpt | 3 ++- tests/PhpGenerator/Printer.phpt | 5 +++-- tests/PhpGenerator/PsrPrinter.phpt | 5 +++-- tests/PhpGenerator/invalidNames.phpt | 4 ---- 7 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index bbd409f6..a5d4c805 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -307,7 +307,7 @@ public function removeImplement(string $name): static /** - * @param string[]|TraitUse[] $traits + * @param TraitUse[] $traits */ public function setTraits(array $traits): static { @@ -315,6 +315,7 @@ public function setTraits(array $traits): static $this->traits = []; foreach ($traits as $trait) { if (!$trait instanceof TraitUse) { + trigger_error(__METHOD__ . '() accepts an array of TraitUse as parameter, string given.', E_USER_DEPRECATED); $trait = new TraitUse($trait); } @@ -372,13 +373,14 @@ public function addMember(Method|Property|Constant|EnumCase|TraitUse $member): s /** - * @param Constant[]|mixed[] $consts + * @param Constant[] $consts */ public function setConstants(array $consts): static { $this->consts = []; foreach ($consts as $k => $const) { if (!$const instanceof Constant) { + trigger_error(__METHOD__ . '() accepts an array of Constant as parameter, ' . get_debug_type($const) . ' given.', E_USER_DEPRECATED); $const = (new Constant($k))->setValue($const)->setPublic(); } diff --git a/tests/PhpGenerator/ClassType.enum.phpt b/tests/PhpGenerator/ClassType.enum.phpt index a7a7cd57..525f0c40 100644 --- a/tests/PhpGenerator/ClassType.enum.phpt +++ b/tests/PhpGenerator/ClassType.enum.phpt @@ -17,11 +17,11 @@ $enum = ClassType::enum('Suit'); Assert::true($enum->isEnum()); -$enum - ->setTraits(['ObjectTrait']) - ->addComment("Description of class.\nThis is example\n") - ->addAttribute('ExampleAttribute') - ->addConstant('ACTIVE', false); +$enum->addComment("Description of class.\nThis is example\n") + ->addAttribute('ExampleAttribute'); + +$enum->addConstant('ACTIVE', false); +$enum->addTrait('ObjectTrait'); $enum->addMethod('foo') ->setBody('return 10;'); diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index b29ac616..e8f4bc35 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -31,13 +31,15 @@ $class ->setExtends('ParentClass') ->addImplement('IExample') ->addImplement('IOne') - ->setTraits(['ObjectTrait']) - ->addTrait('AnotherTrait', ['sayHello as protected']) ->addComment("Description of class.\nThis is example\n /**/") - ->addComment('@property-read Nette\Forms\Form $form') - ->setConstants(['ROLE' => 'admin']) - ->addConstant('ACTIVE', false) - ->setFinal(); + ->addComment('@property-read Nette\Forms\Form $form'); + +$class->addTrait('ObjectTrait'); +$class->addTrait('AnotherTrait', ['sayHello as protected']); + +$class->addConstant('ROLE', 'admin'); +$class->addConstant('ACTIVE', false) + ->setFinal(); Assert::false($class->isFinal()); Assert::true($class->isAbstract()); diff --git a/tests/PhpGenerator/Printer.namespace.phpt b/tests/PhpGenerator/Printer.namespace.phpt index eb6401d3..3a380100 100644 --- a/tests/PhpGenerator/Printer.namespace.phpt +++ b/tests/PhpGenerator/Printer.namespace.phpt @@ -21,9 +21,10 @@ $class = $namespace->addClass('A') ->setExtends('ParentClass') ->addImplement('IExample') ->addImplement('Foo\IOne') - ->setTraits(['Foo\ObjectTrait']) ->addComment("Description of class.\nThis is example\n"); +$class->addTrait('Foo\ObjectTrait'); + $class->addMethod('first') ->addComment('@return resource') ->setFinal(true) diff --git a/tests/PhpGenerator/Printer.phpt b/tests/PhpGenerator/Printer.phpt index 43972d4d..2fc87f10 100644 --- a/tests/PhpGenerator/Printer.phpt +++ b/tests/PhpGenerator/Printer.phpt @@ -18,10 +18,11 @@ $class = (new ClassType('Example')) ->setFinal(true) ->setExtends('ParentClass') ->addImplement('IExample') - ->setTraits(['ObjectTrait']) - ->addTrait('AnotherTrait', ['sayHello as protected']) ->addComment("Description of class.\nThis is example\n"); +$class->addTrait('ObjectTrait'); +$class->addTrait('AnotherTrait', ['sayHello as protected']); + $class->addConstant('FORCE_ARRAY', new Literal('Nette\Utils\Json::FORCE_ARRAY')) ->setPrivate() ->addComment('Commented'); diff --git a/tests/PhpGenerator/PsrPrinter.phpt b/tests/PhpGenerator/PsrPrinter.phpt index 7e4cba93..b98a9b76 100644 --- a/tests/PhpGenerator/PsrPrinter.phpt +++ b/tests/PhpGenerator/PsrPrinter.phpt @@ -17,10 +17,11 @@ $class = (new ClassType('Example')) ->setFinal(true) ->setExtends('ParentClass') ->addImplement('IExample') - ->setTraits(['ObjectTrait']) - ->addTrait('AnotherTrait', ['sayHello as protected']) ->addComment("Description of class.\nThis is example\n"); +$class->addTrait('ObjectTrait'); +$class->addTrait('AnotherTrait', ['sayHello as protected']); + $class->addConstant('FORCE_ARRAY', new Literal('Nette\Utils\Json::FORCE_ARRAY')) ->setVisibility('private') ->addComment('Commented'); diff --git a/tests/PhpGenerator/invalidNames.phpt b/tests/PhpGenerator/invalidNames.phpt index 0ab21b42..5830dc48 100644 --- a/tests/PhpGenerator/invalidNames.phpt +++ b/tests/PhpGenerator/invalidNames.phpt @@ -108,10 +108,6 @@ Assert::exception(function () use ($class) { $class->addImplement('*'); }, Nette\InvalidArgumentException::class, "Value '*' is not valid class name."); -Assert::exception(function () use ($class) { - $class->setTraits(['A', '*']); -}, Nette\InvalidArgumentException::class, "Value '*' is not valid trait name."); - Assert::exception(function () use ($class) { $class->addTrait('*'); }, Nette\InvalidArgumentException::class, "Value '*' is not valid trait name."); From af0099172bf4371255f8489e4c686c0866fde586 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 15 Sep 2021 17:05:57 +0200 Subject: [PATCH 103/266] ClassType::from() & Factory::fromClassReflection(): removed parameter $materializeTraits (BC break) --- src/PhpGenerator/ClassType.php | 7 +- src/PhpGenerator/Factory.php | 36 ++--- tests/PhpGenerator/ClassType.from.trait.phpt | 12 +- ...sType.from.trait-materialize.bodies.expect | 151 ------------------ .../ClassType.from.trait-materialize.expect | 135 ---------------- 5 files changed, 23 insertions(+), 318 deletions(-) delete mode 100644 tests/PhpGenerator/expected/ClassType.from.trait-materialize.bodies.expect delete mode 100644 tests/PhpGenerator/expected/ClassType.from.trait-materialize.expect diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index a5d4c805..69554b5b 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -92,10 +92,13 @@ public static function enum(string $name): self } - public static function from(string|object $class, bool $withBodies = false, bool $materializeTraits = true): self + public static function from(string|object $class, bool $withBodies = false, ?bool $materializeTraits = null): self { + if ($materializeTraits !== null) { + trigger_error(__METHOD__ . '() parameter $materializeTraits has been removed (is always false).', E_USER_DEPRECATED); + } return (new Factory) - ->fromClassReflection(new \ReflectionClass($class), $withBodies, $materializeTraits); + ->fromClassReflection(new \ReflectionClass($class), $withBodies); } diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 7ff49f06..b18a3e16 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -27,8 +27,11 @@ final class Factory public function fromClassReflection( \ReflectionClass $from, bool $withBodies = false, - bool $materializeTraits = true, + ?bool $materializeTraits = null, ): ClassType { + if ($materializeTraits !== null) { + trigger_error(__METHOD__ . '() parameter $materializeTraits has been removed (is always false).', E_USER_DEPRECATED); + } if ($withBodies && $from->isAnonymous()) { throw new Nette\NotSupportedException('The $withBodies parameter cannot be used for anonymous functions.'); } @@ -69,9 +72,7 @@ public function fromClassReflection( $props = []; foreach ($from->getProperties() as $prop) { - $declaringClass = $materializeTraits - ? $prop->getDeclaringClass() - : Reflection::getPropertyDeclaringClass($prop); + $declaringClass = Reflection::getPropertyDeclaringClass($prop); if ($prop->isDefault() && $declaringClass->name === $from->name @@ -86,8 +87,8 @@ public function fromClassReflection( $methods = $resolutions = []; foreach ($from->getMethods() as $method) { - $realMethod = Reflection::getMethodDeclaringMethod($method); - $declaringClass = ($materializeTraits ? $method : $realMethod)->getDeclaringClass(); + $declaringMethod = Reflection::getMethodDeclaringMethod($method); + $declaringClass = $declaringMethod->getDeclaringClass(); if ( $declaringClass->name === $from->name @@ -95,31 +96,28 @@ public function fromClassReflection( ) { $methods[] = $m = $this->fromMethodReflection($method); if ($withBodies) { - $realMethodClass = $realMethod->getDeclaringClass(); - $bodies = &$this->bodyCache[$realMethodClass->name]; - $bodies ??= $this->getExtractor($realMethodClass)->extractMethodBodies($realMethodClass->name); - if (isset($bodies[$realMethod->name])) { - $m->setBody($bodies[$realMethod->name]); + $bodies = &$this->bodyCache[$declaringClass->name]; + $bodies ??= $this->getExtractor($declaringClass)->extractMethodBodies($declaringClass->name); + if (isset($bodies[$declaringMethod->name])) { + $m->setBody($bodies[$declaringMethod->name]); } } } - $modifier = $realMethod->getModifiers() !== $method->getModifiers() + $modifier = $declaringMethod->getModifiers() !== $method->getModifiers() ? ' ' . $this->getVisibility($method) : null; - $alias = $realMethod->name !== $method->name ? ' ' . $method->name : ''; + $alias = $declaringMethod->name !== $method->name ? ' ' . $method->name : ''; if ($modifier || $alias) { - $resolutions[] = $realMethod->name . ' as' . $modifier . $alias; + $resolutions[] = $declaringMethod->name . ' as' . $modifier . $alias; } } $class->setMethods($methods); - if (!$materializeTraits) { - foreach ($from->getTraitNames() as $trait) { - $class->addTrait($trait, $resolutions); - $resolutions = []; - } + foreach ($from->getTraitNames() as $trait) { + $class->addTrait($trait, $resolutions); + $resolutions = []; } $consts = $cases = []; diff --git a/tests/PhpGenerator/ClassType.from.trait.phpt b/tests/PhpGenerator/ClassType.from.trait.phpt index 6ea572c3..d810b851 100644 --- a/tests/PhpGenerator/ClassType.from.trait.phpt +++ b/tests/PhpGenerator/ClassType.from.trait.phpt @@ -22,19 +22,9 @@ $classes = [ $res = array_map(fn($class) => ClassType::from($class), $classes); -sameFile(__DIR__ . '/expected/ClassType.from.trait-materialize.expect', implode("\n", $res)); - - -$res = array_map(fn($class) => ClassType::from($class, withBodies: true), $classes); - -sameFile(__DIR__ . '/expected/ClassType.from.trait-materialize.bodies.expect', implode("\n", $res)); - - -$res = array_map(fn($class) => ClassType::from($class, materializeTraits: false), $classes); - sameFile(__DIR__ . '/expected/ClassType.from.trait-use.expect', implode("\n", $res)); -$res = array_map(fn($class) => ClassType::from($class, withBodies: true, materializeTraits: false), $classes); +$res = array_map(fn($class) => ClassType::from($class, withBodies: true), $classes); sameFile(__DIR__ . '/expected/ClassType.from.trait-use.bodies.expect', implode("\n", $res)); diff --git a/tests/PhpGenerator/expected/ClassType.from.trait-materialize.bodies.expect b/tests/PhpGenerator/expected/ClassType.from.trait-materialize.bodies.expect deleted file mode 100644 index 5812097e..00000000 --- a/tests/PhpGenerator/expected/ClassType.from.trait-materialize.bodies.expect +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Trait1 - */ -trait Trait1 -{ - public static $s1; - public $x1; - - - public function f1() - { - echo 'Trait1::f1'; - } -} - -trait Trait1b -{ - public function f1() - { - echo 'Trait1b::f1'; - } -} - -trait Trait2 -{ - protected $x2; - public static $s1; - public $x1; - - - public function f2() - { - echo 'Trait2::f2'; - } - - - public function f1() - { - echo 'Trait1::f1'; - } -} - -class ParentClass -{ - public $x1; - - - public function f1() - { - echo 'ParentClass::f1'; - } -} - -class Class1 extends ParentClass -{ - protected $x2; - public static $s1; - - - public function f1() - { - echo 'Trait1::f1'; - } - - - public function f2() - { - echo 'Trait2::f2'; - } -} - -class Class2 extends ParentClass -{ - public $x1; - protected $x2; - public static $s1; - - - public function f1() - { - echo 'Class2::f1'; - } - - - public function f2() - { - echo 'Trait2::f2'; - } -} - -class Class3 extends ParentClass -{ - /** info */ - public $x1; - protected $x2; - public static $s1; - - - public function f1() - { - echo 'Class3::f1'; - } - - - public function f2() - { - echo 'Trait2::f2'; - } - - - protected function aliased() - { - echo 'Trait1::f1'; - } -} - -class Class4 extends ParentClass -{ - protected $x2; - public static $s1; - - - public function aliased() - { - echo 'Class4::aliased'; - } - - - public function f1() - { - echo 'Trait1::f1'; - } - - - public function f2() - { - echo 'Trait2::f2'; - } -} - -class Class5 -{ - public static $s1; - public $x1; - - - private function f1() - { - echo 'Trait1b::f1'; - } -} diff --git a/tests/PhpGenerator/expected/ClassType.from.trait-materialize.expect b/tests/PhpGenerator/expected/ClassType.from.trait-materialize.expect deleted file mode 100644 index 8d31386c..00000000 --- a/tests/PhpGenerator/expected/ClassType.from.trait-materialize.expect +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Trait1 - */ -trait Trait1 -{ - public static $s1; - public $x1; - - - public function f1() - { - } -} - -trait Trait1b -{ - public function f1() - { - } -} - -trait Trait2 -{ - protected $x2; - public static $s1; - public $x1; - - - public function f2() - { - } - - - public function f1() - { - } -} - -class ParentClass -{ - public $x1; - - - public function f1() - { - } -} - -class Class1 extends ParentClass -{ - protected $x2; - public static $s1; - - - public function f1() - { - } - - - public function f2() - { - } -} - -class Class2 extends ParentClass -{ - public $x1; - protected $x2; - public static $s1; - - - public function f1() - { - } - - - public function f2() - { - } -} - -class Class3 extends ParentClass -{ - /** info */ - public $x1; - protected $x2; - public static $s1; - - - public function f1() - { - } - - - public function f2() - { - } - - - protected function aliased() - { - } -} - -class Class4 extends ParentClass -{ - protected $x2; - public static $s1; - - - public function aliased() - { - } - - - public function f1() - { - } - - - public function f2() - { - } -} - -class Class5 -{ - public static $s1; - public $x1; - - - private function f1() - { - } -} From 1270e1234d975a84504e7344cc3ba345634388a6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 3 Oct 2021 00:06:05 +0200 Subject: [PATCH 104/266] ClassType::addTrait() returns TraitUse instead of ClassType (BC break) --- readme.md | 2 +- src/PhpGenerator/ClassType.php | 11 +++++------ src/PhpGenerator/TraitUse.php | 23 ++++++++++++++++++++-- tests/PhpGenerator/ClassType.phpt | 5 +++-- tests/PhpGenerator/PhpFile.phpt | 7 ++++--- tests/PhpGenerator/PhpNamespace.fqn.phpt | 14 +++++++------ tests/PhpGenerator/PhpNamespace.print.phpt | 3 ++- tests/PhpGenerator/Printer.phpt | 3 ++- tests/PhpGenerator/PsrPrinter.phpt | 3 ++- 9 files changed, 48 insertions(+), 23 deletions(-) diff --git a/readme.md b/readme.md index 30923a6e..051a8545 100644 --- a/readme.md +++ b/readme.md @@ -314,7 +314,7 @@ Using Traits ```php $class = new Nette\PhpGenerator\ClassType('Demo'); $class->addTrait('SmartObject'); -$class->addTrait('MyTrait', true) +$class->addTrait('MyTrait') ->addResolution('sayHello as protected') ->addComment('@use MyTrait'); echo $class; diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 69554b5b..ab5ccb98 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -343,15 +343,14 @@ public function getTraitResolutions(): array } - public function addTrait(string $name, array|bool $resolutions = []): static|TraitUse + public function addTrait(string $name, array|bool|null $deprecatedParam = null): TraitUse { - $this->traits[$name] = $trait = new TraitUse($name); - if ($resolutions === true) { - return $trait; + $this->traits[$name] = $trait = new TraitUse($name, $this); + if (is_array($deprecatedParam)) { + array_map(fn($item) => $trait->addResolution($item), $deprecatedParam); } - array_map(fn($item) => $trait->addResolution($item), $resolutions); - return $this; + return $trait; } diff --git a/src/PhpGenerator/TraitUse.php b/src/PhpGenerator/TraitUse.php index caae9271..a7ac54c1 100644 --- a/src/PhpGenerator/TraitUse.php +++ b/src/PhpGenerator/TraitUse.php @@ -17,20 +17,25 @@ */ final class TraitUse { - use Nette\SmartObject; + use Nette\SmartObject { + __call as private parentCall; + } use Traits\NameAware; use Traits\CommentAware; private array $resolutions = []; + private ?ClassType $parent; + - public function __construct(string $name) + public function __construct(string $name, ?ClassType $parent = null) { if (!Nette\PhpGenerator\Helpers::isNamespaceIdentifier($name, true)) { throw new Nette\InvalidArgumentException("Value '$name' is not valid trait name."); } $this->name = $name; + $this->parent = $parent; } @@ -45,4 +50,18 @@ public function getResolutions(): array { return $this->resolutions; } + + + public function __call(string $nm, array $args): mixed + { + if (!$this->parent) { + return $this->parentCall($nm, $args); + } + $trace = debug_backtrace(0); + $loc = isset($trace[0]['file']) + ? ' in ' . $trace[0]['file'] . ':' . $trace[0]['line'] + : ''; + trigger_error('The ClassType::addTrait() method now returns a TraitUse object instead of ClassType. Please fix the method chaining' . $loc, E_USER_DEPRECATED); + return $this->parent->$nm(...$args); + } } diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index e8f4bc35..eafc046d 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -35,7 +35,8 @@ $class ->addComment('@property-read Nette\Forms\Form $form'); $class->addTrait('ObjectTrait'); -$class->addTrait('AnotherTrait', ['sayHello as protected']); +$class->addTrait('AnotherTrait') + ->addResolution('sayHello as protected'); $class->addConstant('ROLE', 'admin'); $class->addConstant('ACTIVE', false) @@ -141,7 +142,7 @@ $class->addImplement('foo'); $class->removeImplement('foo'); $class - ->addTrait('ThirdTrait', true) + ->addTrait('ThirdTrait') ->addResolution('a as private foo') ->addResolution('b as private bar') ->addComment('@use Foo'); diff --git a/tests/PhpGenerator/PhpFile.phpt b/tests/PhpGenerator/PhpFile.phpt index 4280ef15..457ecc38 100644 --- a/tests/PhpGenerator/PhpFile.phpt +++ b/tests/PhpGenerator/PhpFile.phpt @@ -32,9 +32,10 @@ Assert::same($namespaceFoo, $traitC->getNamespace()); $classA ->addImplement('Foo\A') - ->addTrait('Foo\C') - ->addImplement('Bar\C') - ->addTrait('Bar\D'); + ->addImplement('Bar\C'); + +$classA->addTrait('Foo\C'); +$classA->addTrait('Bar\D'); $namespaceBar = $file->addNamespace('Bar'); diff --git a/tests/PhpGenerator/PhpNamespace.fqn.phpt b/tests/PhpGenerator/PhpNamespace.fqn.phpt index 5473e555..5615e6c5 100644 --- a/tests/PhpGenerator/PhpNamespace.fqn.phpt +++ b/tests/PhpGenerator/PhpNamespace.fqn.phpt @@ -18,9 +18,10 @@ $class = new ClassType('Example'); $class ->setExtends('\ParentClass') ->addImplement('One') - ->addImplement('\Two') - ->addTrait('Three') - ->addTrait('\Four'); + ->addImplement('\Two'); + +$class->addTrait('Three'); +$class->addTrait('\Four'); $class->addMethod('one') ->setReturnType('One'); @@ -45,9 +46,10 @@ $class = new ClassType('Example', new PhpNamespace('')); $class ->setExtends('\ParentClass') ->addImplement('One') - ->addImplement('\Two') - ->addTrait('Three') - ->addTrait('\Four'); + ->addImplement('\Two'); + +$class->addTrait('Three'); +$class->addTrait('\Four'); $class->addMethod('one') ->setReturnType('One'); diff --git a/tests/PhpGenerator/PhpNamespace.print.phpt b/tests/PhpGenerator/PhpNamespace.print.phpt index 96351893..bcdc3ba8 100644 --- a/tests/PhpGenerator/PhpNamespace.print.phpt +++ b/tests/PhpGenerator/PhpNamespace.print.phpt @@ -20,9 +20,10 @@ $interfaceB = $namespace->addInterface('B'); $classA ->addImplement('Foo\A') ->addImplement('Bar\C') - ->addTrait('Bar\D') ->addAttribute('Foo\A'); +$classA->addTrait('Bar\D'); + $method = $classA->addMethod('test'); $method->addAttribute('Foo\A'); $method->setReturnType('static|Foo\A'); diff --git a/tests/PhpGenerator/Printer.phpt b/tests/PhpGenerator/Printer.phpt index 2fc87f10..e1417ed2 100644 --- a/tests/PhpGenerator/Printer.phpt +++ b/tests/PhpGenerator/Printer.phpt @@ -21,7 +21,8 @@ $class = (new ClassType('Example')) ->addComment("Description of class.\nThis is example\n"); $class->addTrait('ObjectTrait'); -$class->addTrait('AnotherTrait', ['sayHello as protected']); +$class->addTrait('AnotherTrait') + ->addResolution('sayHello as protected'); $class->addConstant('FORCE_ARRAY', new Literal('Nette\Utils\Json::FORCE_ARRAY')) ->setPrivate() diff --git a/tests/PhpGenerator/PsrPrinter.phpt b/tests/PhpGenerator/PsrPrinter.phpt index b98a9b76..05ebc849 100644 --- a/tests/PhpGenerator/PsrPrinter.phpt +++ b/tests/PhpGenerator/PsrPrinter.phpt @@ -20,7 +20,8 @@ $class = (new ClassType('Example')) ->addComment("Description of class.\nThis is example\n"); $class->addTrait('ObjectTrait'); -$class->addTrait('AnotherTrait', ['sayHello as protected']); +$class->addTrait('AnotherTrait') + ->addResolution('sayHello as protected'); $class->addConstant('FORCE_ARRAY', new Literal('Nette\Utils\Json::FORCE_ARRAY')) ->setVisibility('private') From f7899d91f5d0604339dd885054375e5d7a1b8d18 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 22 Sep 2021 11:29:57 +0200 Subject: [PATCH 105/266] ClassType::getTraits() returns TraitUse[] instead of string[] (BC break) --- src/PhpGenerator/ClassType.php | 9 +-------- src/PhpGenerator/Printer.php | 2 +- tests/PhpGenerator/ClassType.addMember.phpt | 2 +- tests/PhpGenerator/ClassType.phpt | 7 +++---- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index ab5ccb98..740a1804 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -329,15 +329,8 @@ public function setTraits(array $traits): static } - /** @return string[] */ + /** @return TraitUse[] */ public function getTraits(): array - { - return array_keys($this->traits); - } - - - /** @internal */ - public function getTraitResolutions(): array { return $this->traits; } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index eddd3285..9249443a 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -135,7 +135,7 @@ public function printClass(ClassType $class, ?PhpNamespace $namespace = null): s : fn($s) => $s; $traits = []; - foreach ($class->getTraitResolutions() as $trait) { + foreach ($class->getTraits() as $trait) { $resolutions = $trait->getResolutions(); $traits[] = Helpers::formatDocComment((string) $trait->getComment()) . 'use ' . $resolver($trait->getName()) diff --git a/tests/PhpGenerator/ClassType.addMember.phpt b/tests/PhpGenerator/ClassType.addMember.phpt index 7d25fa3c..521f7654 100644 --- a/tests/PhpGenerator/ClassType.addMember.phpt +++ b/tests/PhpGenerator/ClassType.addMember.phpt @@ -19,7 +19,7 @@ $class = (new ClassType('Example')) Assert::same(['getHandle' => $method], $class->getMethods()); Assert::same(['handle' => $property], $class->getProperties()); Assert::same(['ROLE' => $const], $class->getConstants()); -Assert::same(['Foo\Bar'], $class->getTraits()); +Assert::same(['Foo\Bar' => $trait], $class->getTraits()); Assert::same('', $method->getBody()); diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index eafc046d..694a3a7f 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -24,7 +24,6 @@ Assert::false($class->isInterface()); Assert::false($class->isTrait()); Assert::same([], $class->getExtends()); Assert::same([], $class->getTraits()); -Assert::same([], $class->getTraitResolutions()); $class ->setAbstract(true) @@ -34,8 +33,8 @@ $class ->addComment("Description of class.\nThis is example\n /**/") ->addComment('@property-read Nette\Forms\Form $form'); -$class->addTrait('ObjectTrait'); -$class->addTrait('AnotherTrait') +$trait1 = $class->addTrait('ObjectTrait'); +$trait2 = $class->addTrait('AnotherTrait') ->addResolution('sayHello as protected'); $class->addConstant('ROLE', 'admin'); @@ -45,7 +44,7 @@ $class->addConstant('ACTIVE', false) Assert::false($class->isFinal()); Assert::true($class->isAbstract()); Assert::same('ParentClass', $class->getExtends()); -Assert::same(['ObjectTrait', 'AnotherTrait'], $class->getTraits()); +Assert::same(['ObjectTrait' => $trait1, 'AnotherTrait' => $trait2], $class->getTraits()); Assert::count(2, $class->getConstants()); Assert::type(Nette\PhpGenerator\Constant::class, $class->getConstants()['ROLE']); From 90424c0d522f5b3a23f85416eacdfed207c04ff5 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 23 Sep 2021 23:59:30 +0200 Subject: [PATCH 106/266] Printer: public properties --- readme.md | 9 +++++---- src/PhpGenerator/Printer.php | 8 ++++---- src/PhpGenerator/PsrPrinter.php | 4 ++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/readme.md b/readme.md index 051a8545..06e4d639 100644 --- a/readme.md +++ b/readme.md @@ -772,9 +772,10 @@ Need to customize printer behavior? Create your own by inheriting the `Printer` ```php class MyPrinter extends Nette\PhpGenerator\Printer { - protected string $indentation = "\t"; - protected int $linesBetweenProperties = 0; - protected int $linesBetweenMethods = 1; - protected string $returnTypeColon = ': '; + public int $wrapLength = 120; + public string $indentation = "\t"; + public int $linesBetweenProperties = 0; + public int $linesBetweenMethods = 2; + public string $returnTypeColon = ': '; } ``` diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 9249443a..3b7fa2e8 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -21,10 +21,10 @@ class Printer use Nette\SmartObject; public int $wrapLength = 120; - protected string $indentation = "\t"; - protected int $linesBetweenProperties = 0; - protected int $linesBetweenMethods = 2; - protected string $returnTypeColon = ': '; + public string $indentation = "\t"; + public int $linesBetweenProperties = 0; + public int $linesBetweenMethods = 2; + public string $returnTypeColon = ': '; protected ?PhpNamespace $namespace = null; protected ?Dumper $dumper; private bool $resolveTypes = true; diff --git a/src/PhpGenerator/PsrPrinter.php b/src/PhpGenerator/PsrPrinter.php index efb3c618..7ce72a26 100644 --- a/src/PhpGenerator/PsrPrinter.php +++ b/src/PhpGenerator/PsrPrinter.php @@ -15,6 +15,6 @@ */ final class PsrPrinter extends Printer { - protected string $indentation = ' '; - protected int $linesBetweenMethods = 1; + public string $indentation = ' '; + public int $linesBetweenMethods = 1; } From 739de57653c5c9ebf86ec656cd74d57b8b6d1932 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 24 Sep 2021 14:05:03 +0200 Subject: [PATCH 107/266] deprecated magic properties (BC break) --- composer.json | 2 +- src/PhpGenerator/ClassType.php | 4 ++-- src/PhpGenerator/Closure.php | 2 +- src/PhpGenerator/GlobalFunction.php | 2 +- src/PhpGenerator/Method.php | 2 +- src/PhpGenerator/Parameter.php | 2 +- src/PhpGenerator/Property.php | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 71358c30..e7e93f59 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ ], "require": { "php": ">=8.0 <8.2", - "nette/utils": "^3.2 || ^4.0" + "nette/utils": "^3.2.7 || ^4.0" }, "require-dev": { "nette/tester": "^2.4", diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 740a1804..fef72fdd 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -15,8 +15,8 @@ /** * Class/Interface/Trait/Enum description. * - * @property Method[] $methods - * @property Property[] $properties + * @property-deprecated Method[] $methods + * @property-deprecated Property[] $properties */ final class ClassType { diff --git a/src/PhpGenerator/Closure.php b/src/PhpGenerator/Closure.php index 7cc7a35c..402a656f 100644 --- a/src/PhpGenerator/Closure.php +++ b/src/PhpGenerator/Closure.php @@ -15,7 +15,7 @@ /** * Closure. * - * @property string $body + * @property-deprecated string $body */ final class Closure { diff --git a/src/PhpGenerator/GlobalFunction.php b/src/PhpGenerator/GlobalFunction.php index 8d55a393..ae1e4f88 100644 --- a/src/PhpGenerator/GlobalFunction.php +++ b/src/PhpGenerator/GlobalFunction.php @@ -15,7 +15,7 @@ /** * Global function. * - * @property string $body + * @property-deprecated string $body */ final class GlobalFunction { diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index 30d0dd1e..676875e4 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -15,7 +15,7 @@ /** * Class method. * - * @property string|null $body + * @property-deprecated string|null $body */ final class Method { diff --git a/src/PhpGenerator/Parameter.php b/src/PhpGenerator/Parameter.php index 11c4c4ee..68397aa9 100644 --- a/src/PhpGenerator/Parameter.php +++ b/src/PhpGenerator/Parameter.php @@ -16,7 +16,7 @@ /** * Function/Method parameter description. * - * @property mixed $defaultValue + * @property-deprecated mixed $defaultValue */ class Parameter { diff --git a/src/PhpGenerator/Property.php b/src/PhpGenerator/Property.php index 34c0605b..d1ffbafa 100644 --- a/src/PhpGenerator/Property.php +++ b/src/PhpGenerator/Property.php @@ -16,7 +16,7 @@ /** * Class property description. * - * @property mixed $value + * @property-deprecated mixed $value */ final class Property { From c48096c492e618c771105a0263ae6800fd01309a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 2 Oct 2021 23:13:52 +0200 Subject: [PATCH 108/266] PhpNamespace::add*() throws exception when class/function already exists (BC break) --- readme.md | 2 +- src/PhpGenerator/PhpNamespace.php | 8 ++++++-- tests/PhpGenerator/PhpNamespace.add.phpt | 10 ++++++++++ tests/PhpGenerator/PhpNamespace.phpt | 10 ++++++++++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 06e4d639..680fbc28 100644 --- a/readme.md +++ b/readme.md @@ -574,7 +574,7 @@ $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` -If the class already exists, it will be overwritten. +If the class already exists, it throws exception. You can define use-statements: diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 143024ff..4afb763e 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -242,7 +242,9 @@ public function add(ClassType $class): static } $lower = strtolower($name); - if ($orig = array_change_key_case($this->aliases[self::NAME_NORMAL])[$lower] ?? null) { + if (isset($this->classes[$lower]) && $this->classes[$lower] !== $class) { + throw new Nette\InvalidStateException("Cannot add '$name', because it already exists."); + } elseif ($orig = array_change_key_case($this->aliases[self::NAME_NORMAL])[$lower] ?? null) { throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig."); } @@ -286,7 +288,9 @@ public function removeClass(string $name): static public function addFunction(string $name): GlobalFunction { $lower = strtolower($name); - if ($orig = array_change_key_case($this->aliases[self::NAME_FUNCTION])[$lower] ?? null) { + if (isset($this->functions[$lower])) { + throw new Nette\InvalidStateException("Cannot add '$name', because it already exists."); + } elseif ($orig = array_change_key_case($this->aliases[self::NAME_FUNCTION])[$lower] ?? null) { throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig."); } diff --git a/tests/PhpGenerator/PhpNamespace.add.phpt b/tests/PhpGenerator/PhpNamespace.add.phpt index f825ac4c..25d64a2c 100644 --- a/tests/PhpGenerator/PhpNamespace.add.phpt +++ b/tests/PhpGenerator/PhpNamespace.add.phpt @@ -33,3 +33,13 @@ class B // namespaces are not changed Assert::null($classA->getNamespace()); Assert::same('X', $classB->getNamespace()->getName()); + + +// duplicity +Assert::noError(function () use ($namespace, $classA) { + $namespace->add($classA); +}); + +Assert::exception(function () use ($namespace) { + $namespace->add(new ClassType('a')); +}, Nette\InvalidStateException::class, "Cannot add 'a', because it already exists."); diff --git a/tests/PhpGenerator/PhpNamespace.phpt b/tests/PhpGenerator/PhpNamespace.phpt index 2fb94a27..5f27a9fb 100644 --- a/tests/PhpGenerator/PhpNamespace.phpt +++ b/tests/PhpGenerator/PhpNamespace.phpt @@ -17,6 +17,10 @@ Assert::same('Foo', $namespace->getName()); $classA = $namespace->addClass('A'); Assert::same($namespace, $classA->getNamespace()); +Assert::exception(function () use ($namespace) { + $namespace->addClass('a'); +}, Nette\InvalidStateException::class, "Cannot add 'a', because it already exists."); + $interfaceB = $namespace->addInterface('B'); Assert::same($namespace, $interfaceB->getNamespace()); @@ -25,7 +29,13 @@ Assert::type(Nette\PhpGenerator\ClassType::class, $namespace->getClasses()['A']) $namespace->removeClass('a'); Assert::count(1, $namespace->getClasses()); + $function = $namespace->addFunction('foo'); + +Assert::exception(function () use ($namespace) { + $namespace->addFunction('Foo'); +}, Nette\InvalidStateException::class, "Cannot add 'Foo', because it already exists."); + Assert::count(1, $namespace->getFunctions()); Assert::same($function, $namespace->getFunctions()['foo']); $namespace->removeFunction('FOO'); From 93166ff2171f19612cda3f006783b91168625c77 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 24 Jan 2022 13:29:42 +0100 Subject: [PATCH 109/266] ClassType::add*() throws exception when method/property/etc already exists (BC break) --- readme.md | 2 +- src/PhpGenerator/ClassType.php | 35 ++++++++++++++++----- tests/PhpGenerator/ClassType.addMember.phpt | 10 +++--- tests/PhpGenerator/ClassType.phpt | 30 ++++++++++++++++-- 4 files changed, 63 insertions(+), 14 deletions(-) diff --git a/readme.md b/readme.md index 680fbc28..5632255a 100644 --- a/readme.md +++ b/readme.md @@ -164,7 +164,7 @@ Readonly properties introduced by PHP 8.1 can be marked via `setReadOnly()`. ------ -If the added property, constant, method or parameter already exist, it will be overwritten. +If the added property, constant, method or parameter already exist, it throws exception. Members can be removed using `removeProperty()`, `removeConstant()`, `removeMethod()` or `removeParameter()`. diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index fef72fdd..45335a82 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -338,6 +338,9 @@ public function getTraits(): array public function addTrait(string $name, array|bool|null $deprecatedParam = null): TraitUse { + if (isset($this->traits[$name])) { + throw new Nette\InvalidStateException("Cannot add trait '$name', because it already exists."); + } $this->traits[$name] = $trait = new TraitUse($name, $this); if (is_array($deprecatedParam)) { array_map(fn($item) => $trait->addResolution($item), $deprecatedParam); @@ -356,13 +359,18 @@ public function removeTrait(string $name): static public function addMember(Method|Property|Constant|EnumCase|TraitUse $member): static { - match (true) { - $member instanceof Method => $this->methods[strtolower($member->getName())] = $member, - $member instanceof Property => $this->properties[$member->getName()] = $member, - $member instanceof Constant => $this->consts[$member->getName()] = $member, - $member instanceof EnumCase => $this->cases[$member->getName()] = $member, - $member instanceof TraitUse => $this->traits[$member->getName()] = $member, + $name = $member->getName(); + [$type, $n] = match (true) { + $member instanceof Method => ['methods', strtolower($name)], + $member instanceof Property => ['properties', $name], + $member instanceof Constant => ['consts', $name], + $member instanceof EnumCase => ['cases', $name], + $member instanceof TraitUse => ['traits', $name], }; + if (isset($this->$type[$n])) { + throw new Nette\InvalidStateException("Cannot add member '$name', because it already exists."); + } + $this->$type[$n] = $member; return $this; } @@ -395,6 +403,9 @@ public function getConstants(): array public function addConstant(string $name, $value): Constant { + if (isset($this->consts[$name])) { + throw new Nette\InvalidStateException("Cannot add constant '$name', because it already exists."); + } return $this->consts[$name] = (new Constant($name)) ->setValue($value) ->setPublic(); @@ -434,6 +445,9 @@ public function getCases(): array /** Adds case to enum */ public function addCase(string $name, string|int|null $value = null): EnumCase { + if (isset($this->cases[$name])) { + throw new Nette\InvalidStateException("Cannot add cases '$name', because it already exists."); + } return $this->cases[$name] = (new EnumCase($name)) ->setValue($value); } @@ -483,6 +497,9 @@ public function getProperty(string $name): Property */ public function addProperty(string $name, $value = null): Property { + if (isset($this->properties[$name])) { + throw new Nette\InvalidStateException("Cannot add property '$name', because it already exists."); + } return $this->properties[$name] = func_num_args() > 1 ? (new Property($name))->setValue($value) : new Property($name); @@ -545,12 +562,16 @@ public function getMethod(string $name): Method public function addMethod(string $name): Method { + $lower = strtolower($name); + if (isset($this->methods[$lower])) { + throw new Nette\InvalidStateException("Cannot add method '$name', because it already exists."); + } $method = new Method($name); if (!$this->isInterface()) { $method->setPublic(); } - return $this->methods[strtolower($name)] = $method; + return $this->methods[$lower] = $method; } diff --git a/tests/PhpGenerator/ClassType.addMember.phpt b/tests/PhpGenerator/ClassType.addMember.phpt index 521f7654..f43abb75 100644 --- a/tests/PhpGenerator/ClassType.addMember.phpt +++ b/tests/PhpGenerator/ClassType.addMember.phpt @@ -10,7 +10,6 @@ require __DIR__ . '/../bootstrap.php'; $class = (new ClassType('Example')) - ->addMember($method = new Nette\PhpGenerator\Method('GETHANDLE')) ->addMember($method = new Nette\PhpGenerator\Method('getHandle')) ->addMember($property = new Nette\PhpGenerator\Property('handle')) ->addMember($const = new Nette\PhpGenerator\Constant('ROLE')) @@ -23,6 +22,9 @@ Assert::same(['Foo\Bar' => $trait], $class->getTraits()); Assert::same('', $method->getBody()); -$class = (new ClassType('Example')) - ->setType('interface') - ->addMember($method = new Nette\PhpGenerator\Method('getHandle')); +// duplicity +$class = new ClassType('Example'); +$class->addMember(new Nette\PhpGenerator\Method('foo')); +Assert::exception(function () use ($class) { + $class->addMember(new Nette\PhpGenerator\Method('FOO')); +}, Nette\InvalidStateException::class, "Cannot add member 'FOO', because it already exists."); diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index 694a3a7f..1fd93b7a 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -173,6 +173,33 @@ Assert::exception(function () { }, Nette\InvalidArgumentException::class, 'Argument must be public|protected|private.'); +// duplicity +$class = new ClassType('Example'); +$class->addConstant('a', 1); +Assert::exception(function () use ($class) { + $class->addConstant('a', 1); +}, Nette\InvalidStateException::class, "Cannot add constant 'a', because it already exists."); + +$class->addProperty('a'); +Assert::exception(function () use ($class) { + $class->addProperty('a'); +}, Nette\InvalidStateException::class, "Cannot add property 'a', because it already exists."); + +$class->addMethod('a'); +Assert::exception(function () use ($class) { + $class->addMethod('a'); +}, Nette\InvalidStateException::class, "Cannot add method 'a', because it already exists."); + +Assert::exception(function () use ($class) { + $class->addMethod('A'); +}, Nette\InvalidStateException::class, "Cannot add method 'A', because it already exists."); + +$class->addTrait('A'); +Assert::exception(function () use ($class) { + $class->addTrait('A'); +}, Nette\InvalidStateException::class, "Cannot add trait 'A', because it already exists."); + + // remove members $class = new ClassType('Example'); $class->addConstant('a', 1); @@ -188,8 +215,7 @@ $class->removeProperty('b')->removeProperty('c'); Assert::same(['a'], array_keys($class->getProperties())); $class->addMethod('a'); -$class->addMethod('A'); $class->addMethod('b'); $class->removeMethod('B')->removeMethod('c'); -Assert::same(['A'], array_keys($class->getMethods())); +Assert::same(['a'], array_keys($class->getMethods())); From b2ed5b58807231223fe97d2b8d530bf255a3d40f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 1 Mar 2022 22:30:06 +0100 Subject: [PATCH 110/266] ClassType::__clone() clones traits --- src/PhpGenerator/ClassType.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 45335a82..70a8535a 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -616,6 +616,7 @@ private function validateNames(array $names): void public function __clone() { $clone = fn($item) => clone $item; + $this->traits = array_map($clone, $this->traits); $this->cases = array_map($clone, $this->cases); $this->consts = array_map($clone, $this->consts); $this->properties = array_map($clone, $this->properties); From f7d123ee2f1294e5f355a55a6559c56a63be7c59 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 1 Mar 2022 22:32:27 +0100 Subject: [PATCH 111/266] strict fixes --- src/PhpGenerator/Dumper.php | 4 ++-- src/PhpGenerator/Extractor.php | 13 +++++++++---- src/PhpGenerator/Helpers.php | 4 ++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 24e7b62e..28bc7f6e 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -100,7 +100,7 @@ private function dumpArray(array &$var, array $parents, int $level, int $column) if (empty($var)) { return '[]'; - } elseif ($level > $this->maxDepth || in_array($var, $parents ?? [], true)) { + } elseif ($level > $this->maxDepth || in_array($var, $parents, true)) { throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.'); } @@ -160,7 +160,7 @@ private function dumpObject($var, array $parents, int $level): string $arr = (array) $var; $space = str_repeat($this->indentation, $level); - if ($level > $this->maxDepth || in_array($var, $parents ?? [], true)) { + if ($level > $this->maxDepth || in_array($var, $parents, true)) { throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.'); } diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 17b8f6ab..d6fa7139 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -166,6 +166,9 @@ public function extractAll(): PhpFile $phpFile = new PhpFile; $namespace = ''; $visitor = new class extends PhpParser\NodeVisitorAbstract { + public $callback; + + public function enterNode(Node $node) { return ($this->callback)($node); @@ -177,7 +180,9 @@ public function enterNode(Node $node) return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; } match (true) { - $node instanceof Node\Stmt\DeclareDeclare && $node->key->name === 'strict_types' => $phpFile->setStrictTypes((bool) $node->value->value), + $node instanceof Node\Stmt\DeclareDeclare + && $node->key->name === 'strict_types' + && $node->value instanceof Node\Scalar\LNumber => $phpFile->setStrictTypes((bool) $node->value->value), $node instanceof Node\Stmt\Namespace_ => $namespace = $node->name?->toString(), $node instanceof Node\Stmt\Use_ => $this->addUseToNamespace($node, $phpFile->addNamespace($namespace)), $node instanceof Node\Stmt\Class_ => $class = $this->addClassToFile($phpFile, $node), @@ -385,7 +390,7 @@ private function setupFunction(GlobalFunction|Method $function, Node\FunctionLik { $function->setReturnReference($node->returnsByRef()); $function->setReturnType($node->getReturnType() ? $this->toPhp($node->getReturnType()) : null); - foreach ($node->params as $item) { + foreach ($node->getParams() as $item) { $param = $function->addParameter($item->var->name); $param->setType($item->type ? $this->toPhp($item->type) : null); $param->setReference($item->byRef); @@ -398,8 +403,8 @@ private function setupFunction(GlobalFunction|Method $function, Node\FunctionLik } $this->addCommentAndAttributes($function, $node); - if ($node->stmts) { - $function->setBody($this->getReformattedContents($node->stmts, 2)); + if ($node->getStmts()) { + $function->setBody($this->getReformattedContents($node->getStmts(), 2)); } } diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 8d835b8a..11a67978 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -37,10 +37,10 @@ final class Helpers 'endforeach' => 1, 'declare' => 1, 'enddeclare' => 1, 'as' => 1, 'try' => 1, 'catch' => 1, 'finally' => 1, 'throw' => 1, 'use' => 1, 'insteadof' => 1, 'global' => 1, 'var' => 1, 'unset' => 1, 'isset' => 1, 'empty' => 1, 'continue' => 1, 'goto' => 1, 'function' => 1, 'const' => 1, 'return' => 1, 'print' => 1, 'yield' => 1, 'list' => 1, - 'switch' => 1, 'endswitch' => 1, 'case' => 1, 'default' => 1, 'break' => 1, 'array' => 1, 'callable' => 1, + 'switch' => 1, 'endswitch' => 1, 'case' => 1, 'default' => 1, 'break' => 1, 'extends' => 1, 'implements' => 1, 'namespace' => 1, 'trait' => 1, 'interface' => 1, 'class' => 1, '__CLASS__' => 1, '__TRAIT__' => 1, '__FUNCTION__' => 1, '__METHOD__' => 1, '__LINE__' => 1, '__FILE__' => 1, '__DIR__' => 1, - '__NAMESPACE__' => 1, 'fn' => 1, 'match' => 1, 'enum' => 1, 'static' => 1, 'abstract' => 1, 'final' => 1, + '__NAMESPACE__' => 1, 'fn' => 1, 'match' => 1, 'enum' => 1, 'abstract' => 1, 'final' => 1, 'private' => 1, 'protected' => 1, 'public' => 1, 'readonly' => 1, // additional reserved class names From 0e53ed2440ab9fe808006853a630190fda32d318 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 2 Mar 2022 01:27:39 +0100 Subject: [PATCH 112/266] improved phpDoc and typehints --- src/PhpGenerator/Attribute.php | 4 ++++ src/PhpGenerator/ClassType.php | 13 +++++++------ src/PhpGenerator/Closure.php | 1 + src/PhpGenerator/Constant.php | 2 +- src/PhpGenerator/Dumper.php | 16 ++++++++++++---- src/PhpGenerator/EnumCase.php | 2 +- src/PhpGenerator/Extractor.php | 22 +++++++++++++++------- src/PhpGenerator/Factory.php | 4 ++++ src/PhpGenerator/Helpers.php | 13 ++++++++----- src/PhpGenerator/Literal.php | 1 + src/PhpGenerator/Method.php | 2 +- src/PhpGenerator/Printer.php | 4 +++- src/PhpGenerator/TraitUse.php | 4 +++- src/PhpGenerator/Traits/AttributeAware.php | 1 + src/PhpGenerator/Traits/FunctionLike.php | 4 +++- src/PhpGenerator/Type.php | 2 +- 16 files changed, 66 insertions(+), 29 deletions(-) diff --git a/src/PhpGenerator/Attribute.php b/src/PhpGenerator/Attribute.php index ba2ebc42..bd112c5e 100644 --- a/src/PhpGenerator/Attribute.php +++ b/src/PhpGenerator/Attribute.php @@ -20,9 +20,12 @@ final class Attribute use Nette\SmartObject; private string $name; + + /** @var mixed[] */ private array $args; + /** @param mixed[] $args */ public function __construct(string $name, array $args) { if (!Helpers::isNamespaceIdentifier($name)) { @@ -40,6 +43,7 @@ public function getName(): string } + /** @return mixed[] */ public function getArguments(): array { return $this->args; diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 70a8535a..a32c64b5 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -55,16 +55,16 @@ final class ClassType /** @var TraitUse[] */ private array $traits = []; - /** @var Constant[] name => Constant */ + /** @var array */ private array $consts = []; - /** @var Property[] name => Property */ + /** @var array */ private array $properties = []; - /** @var Method[] name => Method */ + /** @var array */ private array $methods = []; - /** @var EnumCase[] name => EnumCase */ + /** @var array */ private array $cases = []; @@ -401,7 +401,7 @@ public function getConstants(): array } - public function addConstant(string $name, $value): Constant + public function addConstant(string $name, mixed $value): Constant { if (isset($this->consts[$name])) { throw new Nette\InvalidStateException("Cannot add constant '$name', because it already exists."); @@ -495,7 +495,7 @@ public function getProperty(string $name): Property /** * @param string $name without $ */ - public function addProperty(string $name, $value = null): Property + public function addProperty(string $name, mixed $value = null): Property { if (isset($this->properties[$name])) { throw new Nette\InvalidStateException("Cannot add property '$name', because it already exists."); @@ -603,6 +603,7 @@ public function validate(): void } + /** @param string[] $names */ private function validateNames(array $names): void { foreach ($names as $name) { diff --git a/src/PhpGenerator/Closure.php b/src/PhpGenerator/Closure.php index 402a656f..398e1491 100644 --- a/src/PhpGenerator/Closure.php +++ b/src/PhpGenerator/Closure.php @@ -50,6 +50,7 @@ public function setUses(array $uses): static } + /** @return Parameter[] */ public function getUses(): array { return $this->uses; diff --git a/src/PhpGenerator/Constant.php b/src/PhpGenerator/Constant.php index b8ea0f4b..188b19cd 100644 --- a/src/PhpGenerator/Constant.php +++ b/src/PhpGenerator/Constant.php @@ -27,7 +27,7 @@ final class Constant private bool $final = false; - public function setValue($val): static + public function setValue(mixed $val): static { $this->value = $val; return $this; diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 28bc7f6e..d545de64 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -27,13 +27,14 @@ final class Dumper /** * Returns a PHP representation of a variable. */ - public function dump($var, int $column = 0): string + public function dump(mixed $var, int $column = 0): string { return $this->dumpVar($var, [], 0, $column); } - private function dumpVar(&$var, array $parents = [], int $level = 0, int $column = 0): string + /** @param array $parents */ + private function dumpVar(mixed &$var, array $parents = [], int $level = 0, int $column = 0): string { if ($var === null) { return 'null'; @@ -95,6 +96,10 @@ private static function utf8Ord(string $c): int } + /** + * @param mixed[] $var + * @param array $parents + */ private function dumpArray(array &$var, array $parents, int $level, int $column): string { if (empty($var)) { @@ -130,7 +135,8 @@ private function dumpArray(array &$var, array $parents, int $level, int $column) } - private function dumpObject($var, array $parents, int $level): string + /** @param array $parents */ + private function dumpObject(object $var, array $parents, int $level): string { if ($var instanceof \Serializable) { return 'unserialize(' . $this->dumpString(serialize($var)) . ')'; @@ -200,7 +206,7 @@ private function dumpLiteral(Literal $var, int $level): string /** * Generates PHP statement. Supports placeholders: ? \? $? ->? ::? ...? ...?: ?* */ - public function format(string $statement, ...$args): string + public function format(string $statement, mixed ...$args): string { $tokens = preg_split('#(\.\.\.\?:?|\$\?|->\?|::\?|\\\\\?|\?\*|\?(?!\w))#', $statement, -1, PREG_SPLIT_DELIM_CAPTURE); $res = ''; @@ -239,6 +245,7 @@ public function format(string $statement, ...$args): string } + /** @param mixed[] $var */ private function dumpArguments(array &$var, int $column, bool $named): string { $outInline = $outWrapped = ''; @@ -257,6 +264,7 @@ private function dumpArguments(array &$var, int $column, bool $named): string /** + * @param mixed[] $props * @internal */ public static function createObject(string $class, array $props): object diff --git a/src/PhpGenerator/EnumCase.php b/src/PhpGenerator/EnumCase.php index 193cc09c..eab7ff4b 100644 --- a/src/PhpGenerator/EnumCase.php +++ b/src/PhpGenerator/EnumCase.php @@ -32,7 +32,7 @@ public function setValue(string|int|null $val): static } - public function getValue() + public function getValue(): string|int|null { return $this->value; } diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index d6fa7139..0f35d19a 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -25,6 +25,8 @@ final class Extractor use Nette\SmartObject; private string $code; + + /** @var Node[] */ private array $statements; private PhpParser\PrettyPrinterAbstract $printer; @@ -58,6 +60,7 @@ private function parseCode(string $code): void } + /** @return array */ public function extractMethodBodies(string $className): array { $nodeFinder = new NodeFinder; @@ -91,20 +94,24 @@ public function extractFunctionBody(string $name): ?string } - /** @param Node[] $statements */ - private function getReformattedContents(array $statements, int $level): string + /** @param Node[] $nodes */ + private function getReformattedContents(array $nodes, int $level): string { - $body = $this->getNodeContents(...$statements); - $body = $this->performReplacements($body, $this->prepareReplacements($statements)); + $body = $this->getNodeContents(...$nodes); + $body = $this->performReplacements($body, $this->prepareReplacements($nodes)); return Helpers::unindent($body, $level); } - private function prepareReplacements(array $statements): array + /** + * @param Node[] $nodes + * @return array + */ + private function prepareReplacements(array $nodes): array { - $start = $statements[0]->getStartFilePos(); + $start = $nodes[0]->getStartFilePos(); $replacements = []; - (new NodeFinder)->find($statements, function (Node $node) use (&$replacements, $start) { + (new NodeFinder)->find($nodes, function (Node $node) use (&$replacements, $start) { if ($node instanceof Node\Name\FullyQualified) { if ($node->getAttribute('originalName') instanceof Node\Name) { $of = match (true) { @@ -149,6 +156,7 @@ private function prepareReplacements(array $statements): array } + /** @param array $replacements */ private function performReplacements(string $s, array $replacements): string { usort($replacements, fn($a, $b) => $b[0] <=> $a[0]); diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index b18a3e16..34a1a9ca 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -20,10 +20,14 @@ final class Factory { use Nette\SmartObject; + /** @var string[][] */ private array $bodyCache = []; + + /** @var Extractor[] */ private array $extractorCache = []; + /** @param \ReflectionClass $from */ public function fromClassReflection( \ReflectionClass $from, bool $withBodies = false, diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 11a67978..909818c2 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -49,7 +49,7 @@ final class Helpers /** @deprecated use (new Nette\PhpGenerator\Dumper)->dump() */ - public static function dump($var): string + public static function dump(mixed $var): string { trigger_error(__METHOD__ . '() is deprecated, use (new Nette\PhpGenerator\Dumper)->dump().', E_USER_DEPRECATED); return (new Dumper)->dump($var); @@ -57,7 +57,7 @@ public static function dump($var): string /** @deprecated use (new Nette\PhpGenerator\Dumper)->format() */ - public static function format(string $statement, ...$args): string + public static function format(string $statement, mixed ...$args): string { trigger_error(__METHOD__ . '() is deprecated, use (new Nette\PhpGenerator\Dumper)->format().', E_USER_DEPRECATED); return (new Dumper)->format($statement, ...$args); @@ -119,13 +119,13 @@ public static function unindent(string $s, int $level = 1): string } - public static function isIdentifier($value): bool + public static function isIdentifier(mixed $value): bool { return is_string($value) && preg_match('#^' . self::PHP_IDENT . '$#D', $value); } - public static function isNamespaceIdentifier($value, bool $allowLeadingSlash = false): bool + public static function isNamespaceIdentifier(mixed $value, bool $allowLeadingSlash = false): bool { $re = '#^' . ($allowLeadingSlash ? '\\\\?' : '') . self::PHP_IDENT . '(\\\\' . self::PHP_IDENT . ')*$#D'; return is_string($value) && preg_match($re, $value); @@ -152,7 +152,10 @@ public static function tabsToSpaces(string $s, int $count = 4): string } - /** @internal */ + /** + * @param mixed[] $props + * @internal + */ public static function createObject(string $class, array $props): object { return Dumper::createObject($class, $props); diff --git a/src/PhpGenerator/Literal.php b/src/PhpGenerator/Literal.php index 9f95488e..1d751680 100644 --- a/src/PhpGenerator/Literal.php +++ b/src/PhpGenerator/Literal.php @@ -17,6 +17,7 @@ class Literal { public function __construct( private string $value, + /** @var ?mixed[] */ private ?array $args = null, ) { } diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index 676875e4..d729047c 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -85,7 +85,7 @@ public function isAbstract(): bool /** * @param string $name without $ */ - public function addPromotedParameter(string $name, $defaultValue = null): PromotedParameter + public function addPromotedParameter(string $name, mixed $defaultValue = null): PromotedParameter { $param = new PromotedParameter($name); if (func_num_args() > 1) { diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 3b7fa2e8..192fc92a 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -349,6 +349,7 @@ private function printReturnType(Closure|GlobalFunction|Method $function): strin } + /** @param Attribute[] $attrs */ private function printAttributes(array $attrs, bool $inline = false): string { if (!$attrs) { @@ -383,7 +384,7 @@ protected function indent(string $s): string } - protected function dump($var, int $column = 0): string + protected function dump(mixed $var, int $column = 0): string { $this->dumper->indentation = $this->indentation; $this->dumper->wrapLength = $this->wrapLength; @@ -393,6 +394,7 @@ protected function dump($var, int $column = 0): string } + /** @param string[] $props */ private function joinProperties(array $props): string { return $this->linesBetweenProperties diff --git a/src/PhpGenerator/TraitUse.php b/src/PhpGenerator/TraitUse.php index a7ac54c1..128deace 100644 --- a/src/PhpGenerator/TraitUse.php +++ b/src/PhpGenerator/TraitUse.php @@ -23,8 +23,8 @@ final class TraitUse use Traits\NameAware; use Traits\CommentAware; + /** @var string[] */ private array $resolutions = []; - private ?ClassType $parent; @@ -46,12 +46,14 @@ public function addResolution(string $resolution): static } + /** @return string[] */ public function getResolutions(): array { return $this->resolutions; } + /** @param mixed[] $args */ public function __call(string $nm, array $args): mixed { if (!$this->parent) { diff --git a/src/PhpGenerator/Traits/AttributeAware.php b/src/PhpGenerator/Traits/AttributeAware.php index 56650451..09cad7ce 100644 --- a/src/PhpGenerator/Traits/AttributeAware.php +++ b/src/PhpGenerator/Traits/AttributeAware.php @@ -21,6 +21,7 @@ trait AttributeAware private array $attributes = []; + /** @param mixed[] $args */ public function addAttribute(string $name, array $args = []): static { $this->attributes[] = new Attribute($name, $args); diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index 367a84f9..15bb8e57 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -30,6 +30,7 @@ trait FunctionLike private bool $returnNullable = false; + /** @param ?mixed[] $args */ public function setBody(string $code, ?array $args = null): static { $this->body = $args === null @@ -45,6 +46,7 @@ public function getBody(): string } + /** @param ?mixed[] $args */ public function addBody(string $code, ?array $args = null): static { $this->body .= ($args === null ? $code : (new Dumper)->format($code, ...$args)) . "\n"; @@ -77,7 +79,7 @@ public function getParameters(): array /** * @param string $name without $ */ - public function addParameter(string $name, $defaultValue = null): Parameter + public function addParameter(string $name, mixed $defaultValue = null): Parameter { $param = new Parameter($name); if (func_num_args() > 1) { diff --git a/src/PhpGenerator/Type.php b/src/PhpGenerator/Type.php index b12cf3c1..be77199d 100644 --- a/src/PhpGenerator/Type.php +++ b/src/PhpGenerator/Type.php @@ -53,7 +53,7 @@ public static function intersection(string ...$types): string /** @deprecated use get_debug_type() */ - public static function getType($value): ?string + public static function getType(mixed $value): ?string { trigger_error(__METHOD__ . '() is deprecated, use PHP function get_debug_type()', E_USER_DEPRECATED); return is_resource($value) ? null : get_debug_type($value); From c3e5218e2c99c4c3dd45684141a461db01b3e75d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 2 Mar 2022 01:56:20 +0100 Subject: [PATCH 113/266] added PHPStan baseline --- phpstan-baseline.neon | 81 +++++++++++++++++++++++++++++++++++++++++++ phpstan.neon | 7 ++-- 2 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 phpstan-baseline.neon diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 00000000..3a0b7836 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,81 @@ +parameters: + ignoreErrors: + - + message: '#^Method Nette\\PhpGenerator\\ClassType\:\:addTrait\(\) has parameter \$deprecatedParam with no value type specified in iterable type array\.$#' + count: 1 + path: src/PhpGenerator/ClassType.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: '#^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\.$#' + count: 1 + path: src/PhpGenerator/Extractor.php + + - + message: '#^Variable \$trait might not be defined\.$#' + count: 2 + path: src/PhpGenerator/Extractor.php + + - + message: '#^Call to an undefined method ReflectionClass\\:\:hasCase\(\)\.$#' + count: 1 + path: src/PhpGenerator/Factory.php + + - + message: '#^Elseif branch is unreachable because previous condition is always true\.$#' + count: 1 + path: src/PhpGenerator/Factory.php + + - + message: '#^Method Nette\\PhpGenerator\\Factory\:\:getAttributes\(\) has parameter \$from with no type specified\.$#' + count: 1 + path: src/PhpGenerator/Factory.php + + - + message: '#^Method Nette\\PhpGenerator\\Factory\:\:getAttributes\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: src/PhpGenerator/Factory.php + + - + message: '#^Method Nette\\PhpGenerator\\Factory\:\:getExtractor\(\) has parameter \$from with no type specified\.$#' + count: 1 + path: src/PhpGenerator/Factory.php + + - + message: '#^Method Nette\\PhpGenerator\\Factory\:\:getVisibility\(\) has parameter \$from with no type specified\.$#' + count: 1 + path: src/PhpGenerator/Factory.php + + - + message: '#^Unreachable statement \- code above always terminates\.$#' + count: 1 + path: src/PhpGenerator/Factory.php + + - + message: '#^Variable \$bodies on left side of \?\?\= always exists and is not nullable\.$#' + count: 1 + path: src/PhpGenerator/Factory.php + + - + message: '#^Method Nette\\PhpGenerator\\Helpers\:\:formatArgs\(\) has parameter \$args with no value type specified in iterable type array\.$#' + count: 1 + path: src/PhpGenerator/Helpers.php + + - + message: '#^Method Nette\\PhpGenerator\\Method\:\:from\(\) has parameter \$method with no value type specified in iterable type array\.$#' + count: 1 + path: src/PhpGenerator/Method.php diff --git a/phpstan.neon b/phpstan.neon index d7ffa1b5..bc3a10d6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,7 +1,8 @@ +includes: + - phpstan-baseline.neon + parameters: - level: 5 + level: 6 paths: - src - - treatPhpDocTypesAsCertain: false From d003c62c1b579c1ad520cbfaed8a324edfbd4a53 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 1 Mar 2022 23:41:09 +0100 Subject: [PATCH 114/266] ClassType divided into InterfaceType, TraitType & EnumType --- phpstan-baseline.neon | 40 ++ readme.md | 7 +- src/PhpGenerator/ClassLike.php | 134 +++++ src/PhpGenerator/ClassType.php | 459 ++---------------- src/PhpGenerator/EnumType.php | 130 +++++ src/PhpGenerator/Extractor.php | 24 +- src/PhpGenerator/Factory.php | 32 +- src/PhpGenerator/InterfaceType.php | 77 +++ src/PhpGenerator/PhpFile.php | 8 +- src/PhpGenerator/PhpNamespace.php | 21 +- src/PhpGenerator/Printer.php | 124 +++-- src/PhpGenerator/TraitType.php | 53 ++ src/PhpGenerator/TraitUse.php | 4 +- src/PhpGenerator/Traits/ConstantsAware.php | 65 +++ src/PhpGenerator/Traits/MethodsAware.php | 87 ++++ src/PhpGenerator/Traits/PropertiesAware.php | 79 +++ src/PhpGenerator/Traits/TraitsAware.php | 69 +++ tests/PhpGenerator/ClassType.enum.phpt | 6 +- tests/PhpGenerator/ClassType.fromCode.phpt | 8 +- tests/PhpGenerator/ClassType.interface.phpt | 4 +- tests/PhpGenerator/ClassType.phpt | 2 +- tests/PhpGenerator/Dumper.dump().phpt | 6 +- tests/PhpGenerator/Factory.fromClassCode.phpt | 2 +- tests/PhpGenerator/PhpFile.phpt | 2 +- tests/PhpGenerator/invalidNames.phpt | 18 +- 25 files changed, 933 insertions(+), 528 deletions(-) create mode 100644 src/PhpGenerator/ClassLike.php create mode 100644 src/PhpGenerator/EnumType.php create mode 100644 src/PhpGenerator/InterfaceType.php create mode 100644 src/PhpGenerator/TraitType.php create mode 100644 src/PhpGenerator/Traits/ConstantsAware.php create mode 100644 src/PhpGenerator/Traits/MethodsAware.php create mode 100644 src/PhpGenerator/Traits/PropertiesAware.php create mode 100644 src/PhpGenerator/Traits/TraitsAware.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3a0b7836..dd03b4fa 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,10 +1,20 @@ 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 path: src/PhpGenerator/ClassType.php + - + message: '#^Method Nette\\PhpGenerator\\EnumType\:\:addTrait\(\) has parameter \$deprecatedParam with no value type specified in iterable type array\.$#' + count: 1 + path: src/PhpGenerator/EnumType.php + - message: '#^Access to an undefined property PhpParser\\Node\:\:\$attrGroups\.$#' count: 1 @@ -15,6 +25,26 @@ parameters: count: 1 path: src/PhpGenerator/Extractor.php + - + message: '#^Call to an undefined method Nette\\PhpGenerator\\ClassLike\:\:addConstant\(\)\.$#' + count: 1 + path: src/PhpGenerator/Extractor.php + + - + message: '#^Call to an undefined method Nette\\PhpGenerator\\ClassLike\:\:addMethod\(\)\.$#' + count: 1 + path: src/PhpGenerator/Extractor.php + + - + message: '#^Call to an undefined method Nette\\PhpGenerator\\ClassLike\:\:addProperty\(\)\.$#' + count: 1 + path: src/PhpGenerator/Extractor.php + + - + message: '#^Call to an undefined method Nette\\PhpGenerator\\ClassLike\:\:addTrait\(\)\.$#' + count: 1 + path: src/PhpGenerator/Extractor.php + - message: '#^Method Nette\\PhpGenerator\\Extractor\:\:addCommentAndAttributes\(\) has parameter \$element with no type specified\.$#' count: 1 @@ -60,6 +90,11 @@ 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 @@ -79,3 +114,8 @@ parameters: message: '#^Method Nette\\PhpGenerator\\Method\:\:from\(\) has parameter \$method with no value type specified in iterable type array\.$#' count: 1 path: src/PhpGenerator/Method.php + + - + message: '#^Method Nette\\PhpGenerator\\TraitType\:\:addTrait\(\) has parameter \$deprecatedParam with no value type specified in iterable type array\.$#' + count: 1 + path: src/PhpGenerator/TraitType.php diff --git a/readme.md b/readme.md index 5632255a..3784c0e1 100644 --- a/readme.md +++ b/readme.md @@ -226,9 +226,8 @@ Interface or Trait You can create interfaces and traits: ```php -$interface = Nette\PhpGenerator\ClassType::interface('MyInterface'); -$trait = Nette\PhpGenerator\ClassType::trait('MyTrait'); -// in a similar way $class = Nette\PhpGenerator\ClassType::class('MyClass'); +$interface = Nette\PhpGenerator\InterfaceType('MyInterface'); +$trait = Nette\PhpGenerator\TraitType('MyTrait'); ``` Enums @@ -237,7 +236,7 @@ Enums You can easily create the enums that PHP 8.1 brings: ```php -$enum = Nette\PhpGenerator\ClassType::enum('Suit'); +$enum = Nette\PhpGenerator\EnumType('Suit'); $enum->addCase('Clubs'); $enum->addCase('Diamonds'); $enum->addCase('Hearts'); diff --git a/src/PhpGenerator/ClassLike.php b/src/PhpGenerator/ClassLike.php new file mode 100644 index 00000000..e1e50bd0 --- /dev/null +++ b/src/PhpGenerator/ClassLike.php @@ -0,0 +1,134 @@ +fromClassReflection(new \ReflectionClass($class), $withBodies); + } + + + /** @deprecated use from(..., withBodies: true) */ + public static function withBodiesFrom(string|object $class): self + { + trigger_error(__METHOD__ . '() is deprecated, use from(..., withBodies: true)', E_USER_DEPRECATED); + return (new Factory) + ->fromClassReflection(new \ReflectionClass($class), withBodies: true); + } + + + public static function fromCode(string $code): self + { + return (new Factory) + ->fromClassCode($code); + } + + + public function __construct(string $name, ?PhpNamespace $namespace = null) + { + $this->setName($name); + $this->namespace = $namespace; + } + + + public function __toString(): string + { + return (new Printer)->printClass($this, $this->namespace); + } + + + /** @deprecated an object can be in multiple namespaces */ + public function getNamespace(): ?PhpNamespace + { + return $this->namespace; + } + + + public function setName(?string $name): static + { + if ($name !== null && (!Helpers::isIdentifier($name) || isset(Helpers::KEYWORDS[strtolower($name)]))) { + throw new Nette\InvalidArgumentException("Value '$name' is not valid class name."); + } + + $this->name = $name; + return $this; + } + + + public function getName(): ?string + { + return $this->name; + } + + + public function isClass(): bool + { + return $this instanceof ClassType; + } + + + public function isInterface(): bool + { + return $this instanceof InterfaceType; + } + + + public function isTrait(): bool + { + return $this instanceof TraitType; + } + + + public function isEnum(): bool + { + return $this instanceof EnumType; + } + + + /** @param string[] $names */ + protected function validateNames(array $names): void + { + foreach ($names as $name) { + if (!Helpers::isNamespaceIdentifier($name, allowLeadingSlash: true)) { + throw new Nette\InvalidArgumentException("Value '$name' is not valid class name."); + } + } + } + + + public function validate(): void + { + } +} diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index a32c64b5..bc240800 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -13,152 +13,78 @@ /** - * Class/Interface/Trait/Enum description. + * Class description. * * @property-deprecated Method[] $methods * @property-deprecated Property[] $properties */ -final class ClassType +final class ClassType extends ClassLike { - use Nette\SmartObject; - use Traits\CommentAware; - use Traits\AttributeAware; + use Traits\ConstantsAware; + use Traits\MethodsAware; + use Traits\PropertiesAware; + use Traits\TraitsAware; + /** @deprecated */ public const TYPE_CLASS = 'class', TYPE_INTERFACE = 'interface', TYPE_TRAIT = 'trait', TYPE_ENUM = 'enum'; - public const - VISIBILITY_PUBLIC = 'public', - VISIBILITY_PROTECTED = 'protected', - VISIBILITY_PRIVATE = 'private'; - - private ?PhpNamespace $namespace; - - private ?string $name; - - /** class|interface|trait */ private string $type = self::TYPE_CLASS; private bool $final = false; - private bool $abstract = false; - - /** @var string|string[] */ - private string|array $extends = []; + private ?string $extends = null; /** @var string[] */ private array $implements = []; - /** @var TraitUse[] */ - private array $traits = []; - - /** @var array */ - private array $consts = []; - - /** @var array */ - private array $properties = []; - - /** @var array */ - private array $methods = []; - - /** @var array */ - private array $cases = []; - + /** @deprecated create object using 'new Nette\PhpGenerator\ClassType' */ public static function class(?string $name): self { return new self($name); } - public static function interface(string $name): self + /** @deprecated create object using 'new Nette\PhpGenerator\InterfaceType' */ + public static function interface(string $name): InterfaceType { - return (new self($name))->setType(self::TYPE_INTERFACE); + return new InterfaceType($name); } - public static function trait(string $name): self + /** @deprecated create object using 'new Nette\PhpGenerator\TraitType' */ + public static function trait(string $name): TraitType { - return (new self($name))->setType(self::TYPE_TRAIT); + return new TraitType($name); } - public static function enum(string $name): self + /** @deprecated create object using 'new Nette\PhpGenerator\EnumType' */ + public static function enum(string $name): EnumType { - return (new self($name))->setType(self::TYPE_ENUM); - } - - - public static function from(string|object $class, bool $withBodies = false, ?bool $materializeTraits = null): self - { - if ($materializeTraits !== null) { - trigger_error(__METHOD__ . '() parameter $materializeTraits has been removed (is always false).', E_USER_DEPRECATED); - } - return (new Factory) - ->fromClassReflection(new \ReflectionClass($class), $withBodies); - } - - - /** @deprecated use ClassType::from(..., withBodies: true) */ - public static function withBodiesFrom(string|object $class): self - { - trigger_error(__METHOD__ . '() is deprecated, use ClassType::from(..., withBodies: true)', E_USER_DEPRECATED); - return (new Factory) - ->fromClassReflection(new \ReflectionClass($class), withBodies: true); - } - - - public static function fromCode(string $code): self - { - return (new Factory) - ->fromClassCode($code); + return new EnumType($name); } public function __construct(?string $name = null, ?PhpNamespace $namespace = null) { - $this->setName($name); - $this->namespace = $namespace; - } - - - public function __toString(): string - { - return (new Printer)->printClass($this, $this->namespace); - } - - - /** @deprecated an object can be in multiple namespaces */ - public function getNamespace(): ?PhpNamespace - { - return $this->namespace; - } - - - public function setName(?string $name): static - { - if ($name !== null && (!Helpers::isIdentifier($name) || isset(Helpers::KEYWORDS[strtolower($name)]))) { - throw new Nette\InvalidArgumentException("Value '$name' is not valid class name."); + if ($name === null) { + parent::__construct('foo', $namespace); + $this->setName(null); + } else { + parent::__construct($name, $namespace); } - - $this->name = $name; - return $this; - } - - - public function getName(): ?string - { - return $this->name; } - /** @deprecated use setType('class') or create using ClassType::class() */ + /** @deprecated */ public function setClass(): static { - trigger_error(__METHOD__ . "() is deprecated, use setType('class').", E_USER_DEPRECATED); + trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED); $this->type = self::TYPE_CLASS; return $this; } @@ -170,10 +96,10 @@ public function isClass(): bool } - /** @deprecated use setType('interface') or create using ClassType::interface() */ + /** @deprecated create object using 'new Nette\PhpGenerator\InterfaceType' */ public function setInterface(): static { - trigger_error(__METHOD__ . '() is deprecated, use $class->setType($class::TYPE_INTERFACE) or create object using ClassType::interface()', E_USER_DEPRECATED); + trigger_error(__METHOD__ . "() is deprecated, create object using 'new Nette\\PhpGenerator\\InterfaceType'", E_USER_DEPRECATED); $this->type = self::TYPE_INTERFACE; return $this; } @@ -185,10 +111,10 @@ public function isInterface(): bool } - /** @deprecated use setType('trait') or create using ClassType::trait() */ + /** @deprecated create object using 'new Nette\PhpGenerator\TraitType' */ public function setTrait(): static { - trigger_error(__METHOD__ . '() is deprecated, use $class->setType($class::TYPE_TRAIT) or create object using ClassType::trait()', E_USER_DEPRECATED); + trigger_error(__METHOD__ . "() is deprecated, create object using 'new Nette\\PhpGenerator\\TraitType'", E_USER_DEPRECATED); $this->type = self::TYPE_TRAIT; return $this; } @@ -200,16 +126,13 @@ public function isTrait(): bool } - public function isEnum(): bool - { - return $this->type === self::TYPE_ENUM; - } - - + /** @deprecated create object using 'new Nette\PhpGenerator\InterfaceType' or 'TraitType' */ public function setType(string $type): static { - if (!in_array($type, [self::TYPE_CLASS, self::TYPE_INTERFACE, self::TYPE_TRAIT, self::TYPE_ENUM], true)) { - throw new Nette\InvalidArgumentException('Argument must be class|interface|trait|enum.'); + $upper = ucfirst($type); + trigger_error(__METHOD__ . "() is deprecated, create object using 'new Nette\\PhpGenerator\\{$upper}Type'", E_USER_DEPRECATED); + if (!in_array($type, [self::TYPE_CLASS, self::TYPE_INTERFACE, self::TYPE_TRAIT], true)) { + throw new Nette\InvalidArgumentException('Argument must be class|interface|trait.'); } $this->type = $type; @@ -217,6 +140,7 @@ public function setType(string $type): static } + /** @deprecated */ public function getType(): string { return $this->type; @@ -249,33 +173,22 @@ public function isAbstract(): bool } - /** - * @param string|string[] $names - */ - public function setExtends(string|array $names): static + public function setExtends(?string $name): static { - $this->validateNames((array) $names); - $this->extends = $names; + if ($name) { + $this->validateNames([$name]); + } + $this->extends = $name; return $this; } - /** @return string|string[] */ - public function getExtends(): string|array + public function getExtends(): ?string { return $this->extends; } - public function addExtend(string $name): static - { - $this->validateNames([$name]); - $this->extends = (array) $this->extends; - $this->extends[] = $name; - return $this; - } - - /** * @param string[] $names */ @@ -309,62 +222,13 @@ public function removeImplement(string $name): static } - /** - * @param TraitUse[] $traits - */ - public function setTraits(array $traits): static - { - (function (TraitUse|string ...$traits) {})(...$traits); - $this->traits = []; - foreach ($traits as $trait) { - if (!$trait instanceof TraitUse) { - trigger_error(__METHOD__ . '() accepts an array of TraitUse as parameter, string given.', E_USER_DEPRECATED); - $trait = new TraitUse($trait); - } - - $this->traits[$trait->getName()] = $trait; - } - - return $this; - } - - - /** @return TraitUse[] */ - public function getTraits(): array - { - return $this->traits; - } - - - public function addTrait(string $name, array|bool|null $deprecatedParam = null): TraitUse - { - if (isset($this->traits[$name])) { - throw new Nette\InvalidStateException("Cannot add trait '$name', because it already exists."); - } - $this->traits[$name] = $trait = new TraitUse($name, $this); - if (is_array($deprecatedParam)) { - array_map(fn($item) => $trait->addResolution($item), $deprecatedParam); - } - - return $trait; - } - - - public function removeTrait(string $name): static - { - unset($this->traits[$name]); - return $this; - } - - - public function addMember(Method|Property|Constant|EnumCase|TraitUse $member): static + 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 Constant => ['consts', $name], - $member instanceof EnumCase => ['cases', $name], $member instanceof TraitUse => ['traits', $name], }; if (isset($this->$type[$n])) { @@ -375,241 +239,15 @@ public function addMember(Method|Property|Constant|EnumCase|TraitUse $member): s } - /** - * @param Constant[] $consts - */ - public function setConstants(array $consts): static - { - $this->consts = []; - foreach ($consts as $k => $const) { - if (!$const instanceof Constant) { - trigger_error(__METHOD__ . '() accepts an array of Constant as parameter, ' . get_debug_type($const) . ' given.', E_USER_DEPRECATED); - $const = (new Constant($k))->setValue($const)->setPublic(); - } - - $this->consts[$const->getName()] = $const; - } - - return $this; - } - - - /** @return Constant[] */ - public function getConstants(): array - { - return $this->consts; - } - - - public function addConstant(string $name, mixed $value): Constant - { - if (isset($this->consts[$name])) { - throw new Nette\InvalidStateException("Cannot add constant '$name', because it already exists."); - } - return $this->consts[$name] = (new Constant($name)) - ->setValue($value) - ->setPublic(); - } - - - public function removeConstant(string $name): static - { - unset($this->consts[$name]); - return $this; - } - - - /** - * Sets cases to enum - * @param EnumCase[] $cases - */ - public function setCases(array $cases): static - { - (function (EnumCase ...$cases) {})(...$cases); - $this->cases = []; - foreach ($cases as $case) { - $this->cases[$case->getName()] = $case; - } - - return $this; - } - - - /** @return EnumCase[] */ - public function getCases(): array - { - return $this->cases; - } - - - /** Adds case to enum */ - public function addCase(string $name, string|int|null $value = null): EnumCase - { - if (isset($this->cases[$name])) { - throw new Nette\InvalidStateException("Cannot add cases '$name', because it already exists."); - } - return $this->cases[$name] = (new EnumCase($name)) - ->setValue($value); - } - - - public function removeCase(string $name): static - { - unset($this->cases[$name]); - return $this; - } - - - /** - * @param Property[] $props - */ - public function setProperties(array $props): static - { - (function (Property ...$props) {})(...$props); - $this->properties = []; - foreach ($props as $v) { - $this->properties[$v->getName()] = $v; - } - - return $this; - } - - - /** @return Property[] */ - public function getProperties(): array - { - return $this->properties; - } - - - public function getProperty(string $name): Property - { - if (!isset($this->properties[$name])) { - throw new Nette\InvalidArgumentException("Property '$name' not found."); - } - - return $this->properties[$name]; - } - - - /** - * @param string $name without $ - */ - public function addProperty(string $name, mixed $value = null): Property - { - if (isset($this->properties[$name])) { - throw new Nette\InvalidStateException("Cannot add property '$name', because it already exists."); - } - return $this->properties[$name] = func_num_args() > 1 - ? (new Property($name))->setValue($value) - : new Property($name); - } - - - /** - * @param string $name without $ - */ - public function removeProperty(string $name): static - { - unset($this->properties[$name]); - return $this; - } - - - public function hasProperty(string $name): bool - { - return isset($this->properties[$name]); - } - - - /** - * @param Method[] $methods - */ - public function setMethods(array $methods): static - { - (function (Method ...$methods) {})(...$methods); - $this->methods = []; - foreach ($methods as $m) { - $this->methods[strtolower($m->getName())] = $m; - } - - return $this; - } - - - /** @return Method[] */ - public function getMethods(): array - { - $res = []; - foreach ($this->methods as $m) { - $res[$m->getName()] = $m; - } - - return $res; - } - - - public function getMethod(string $name): Method - { - $m = $this->methods[strtolower($name)] ?? null; - if (!$m) { - throw new Nette\InvalidArgumentException("Method '$name' not found."); - } - - return $m; - } - - - public function addMethod(string $name): Method - { - $lower = strtolower($name); - if (isset($this->methods[$lower])) { - throw new Nette\InvalidStateException("Cannot add method '$name', because it already exists."); - } - $method = new Method($name); - if (!$this->isInterface()) { - $method->setPublic(); - } - - return $this->methods[$lower] = $method; - } - - - public function removeMethod(string $name): static - { - unset($this->methods[strtolower($name)]); - return $this; - } - - - public function hasMethod(string $name): bool - { - return isset($this->methods[strtolower($name)]); - } - - /** @throws Nette\InvalidStateException */ public function validate(): void { - if ($this->isEnum() && ($this->abstract || $this->final || $this->extends || $this->properties)) { - throw new Nette\InvalidStateException("Enum '$this->name' cannot be abstract or final or extends class or have properties."); - - } elseif (!$this->name && ($this->abstract || $this->final)) { + $name = $this->getName(); + if ($name === null && ($this->abstract || $this->final)) { throw new Nette\InvalidStateException('Anonymous class cannot be abstract or final.'); } elseif ($this->abstract && $this->final) { - throw new Nette\InvalidStateException("Class '$this->name' cannot be abstract and final at the same time."); - } - } - - - /** @param string[] $names */ - private function validateNames(array $names): void - { - foreach ($names as $name) { - if (!Helpers::isNamespaceIdentifier($name, allowLeadingSlash: true)) { - throw new Nette\InvalidArgumentException("Value '$name' is not valid class name."); - } + throw new Nette\InvalidStateException("Class '$name' cannot be abstract and final at the same time."); } } @@ -617,10 +255,9 @@ private function validateNames(array $names): void public function __clone() { $clone = fn($item) => clone $item; - $this->traits = array_map($clone, $this->traits); - $this->cases = array_map($clone, $this->cases); $this->consts = array_map($clone, $this->consts); - $this->properties = array_map($clone, $this->properties); $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/EnumType.php b/src/PhpGenerator/EnumType.php new file mode 100644 index 00000000..e24a6d39 --- /dev/null +++ b/src/PhpGenerator/EnumType.php @@ -0,0 +1,130 @@ + */ + private array $cases = []; + + + /** + * @param string[] $names + */ + public function setImplements(array $names): static + { + $this->validateNames($names); + $this->implements = $names; + return $this; + } + + + /** @return string[] */ + public function getImplements(): array + { + return $this->implements; + } + + + public function addImplement(string $name): static + { + $this->validateNames([$name]); + $this->implements[] = $name; + return $this; + } + + + public function removeImplement(string $name): static + { + $this->implements = array_diff($this->implements, [$name]); + return $this; + } + + + /** + * Sets cases to enum + * @param EnumCase[] $cases + */ + public function setCases(array $cases): static + { + (function (EnumCase ...$cases) {})(...$cases); + $this->cases = []; + foreach ($cases as $case) { + $this->cases[$case->getName()] = $case; + } + + return $this; + } + + + /** @return EnumCase[] */ + public function getCases(): array + { + return $this->cases; + } + + + /** Adds case to enum */ + public function addCase(string $name, string|int|null $value = null): EnumCase + { + if (isset($this->cases[$name])) { + throw new Nette\InvalidStateException("Cannot add cases '$name', because it already exists."); + } + return $this->cases[$name] = (new EnumCase($name)) + ->setValue($value); + } + + + public function removeCase(string $name): static + { + unset($this->cases[$name]); + return $this; + } + + + public function addMember(Method|Constant|EnumCase|TraitUse $member): static + { + $name = $member->getName(); + [$type, $n] = match (true) { + $member instanceof Constant => ['consts', $name], + $member instanceof Method => ['methods', strtolower($name)], + $member instanceof TraitUse => ['traits', $name], + $member instanceof EnumCase => ['cases', $name], + }; + if (isset($this->$type[$n])) { + throw new Nette\InvalidStateException("Cannot add member '$name', because it already exists."); + } + $this->$type[$n] = $member; + return $this; + } + + + public function __clone() + { + $clone = fn($item) => clone $item; + $this->consts = array_map($clone, $this->consts); + $this->methods = array_map($clone, $this->methods); + $this->traits = array_map($clone, $this->traits); + $this->cases = array_map($clone, $this->cases); + } +} diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 0f35d19a..f641276a 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -252,7 +252,7 @@ private function addClassToFile(PhpFile $phpFile, Node\Stmt\Class_ $node): Class } - private function addInterfaceToFile(PhpFile $phpFile, Node\Stmt\Interface_ $node): ClassType + private function addInterfaceToFile(PhpFile $phpFile, Node\Stmt\Interface_ $node): InterfaceType { $class = $phpFile->addInterface($node->namespacedName->toString()); foreach ($node->extends as $item) { @@ -264,7 +264,7 @@ private function addInterfaceToFile(PhpFile $phpFile, Node\Stmt\Interface_ $node } - private function addTraitToFile(PhpFile $phpFile, Node\Stmt\Trait_ $node): ClassType + private function addTraitToFile(PhpFile $phpFile, Node\Stmt\Trait_ $node): TraitType { $class = $phpFile->addTrait($node->namespacedName->toString()); $this->addCommentAndAttributes($class, $node); @@ -272,15 +272,15 @@ private function addTraitToFile(PhpFile $phpFile, Node\Stmt\Trait_ $node): Class } - private function addEnumToFile(PhpFile $phpFile, Node\Stmt\Enum_ $node): ClassType + private function addEnumToFile(PhpFile $phpFile, Node\Stmt\Enum_ $node): EnumType { - $class = $phpFile->addEnum($node->namespacedName->toString()); + $enum = $phpFile->addEnum($node->namespacedName->toString()); foreach ($node->implements as $item) { - $class->addImplement($item->toString()); + $enum->addImplement($item->toString()); } - $this->addCommentAndAttributes($class, $node); - return $class; + $this->addCommentAndAttributes($enum, $node); + return $enum; } @@ -291,7 +291,7 @@ private function addFunctionToFile(PhpFile $phpFile, Node\Stmt\Function_ $node): } - private function addTraitToClass(ClassType $class, Node\Stmt\TraitUse $node): void + private function addTraitToClass(ClassLike $class, Node\Stmt\TraitUse $node): void { foreach ($node->traits as $item) { $trait = $class->addTrait($item->toString(), true); @@ -305,7 +305,7 @@ private function addTraitToClass(ClassType $class, Node\Stmt\TraitUse $node): vo } - private function addPropertyToClass(ClassType $class, Node\Stmt\Property $node): void + private function addPropertyToClass(ClassLike $class, Node\Stmt\Property $node): void { foreach ($node->props as $item) { $prop = $class->addProperty($item->name->toString()); @@ -327,7 +327,7 @@ private function addPropertyToClass(ClassType $class, Node\Stmt\Property $node): } - private function addMethodToClass(ClassType $class, Node\Stmt\ClassMethod $node): void + private function addMethodToClass(ClassLike $class, Node\Stmt\ClassMethod $node): void { $method = $class->addMethod($node->name->toString()); $method->setAbstract($node->isAbstract()); @@ -343,7 +343,7 @@ private function addMethodToClass(ClassType $class, Node\Stmt\ClassMethod $node) } - private function addConstantToClass(ClassType $class, Node\Stmt\ClassConst $node): void + private function addConstantToClass(ClassLike $class, Node\Stmt\ClassConst $node): void { foreach ($node->consts as $item) { $value = $this->getReformattedContents([$item->value], 1); @@ -360,7 +360,7 @@ private function addConstantToClass(ClassType $class, Node\Stmt\ClassConst $node } - private function addEnumCaseToClass(ClassType $class, Node\Stmt\EnumCase $node): void + private function addEnumCaseToClass(EnumType $class, Node\Stmt\EnumCase $node): void { $case = $class->addCase($node->name->toString(), $node->expr?->value); $this->addCommentAndAttributes($case, $node); diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 34a1a9ca..b816c604 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -32,7 +32,7 @@ public function fromClassReflection( \ReflectionClass $from, bool $withBodies = false, ?bool $materializeTraits = null, - ): ClassType { + ): ClassLike { if ($materializeTraits !== null) { trigger_error(__METHOD__ . '() parameter $materializeTraits has been removed (is always false).', E_USER_DEPRECATED); } @@ -40,19 +40,21 @@ public function fromClassReflection( throw new Nette\NotSupportedException('The $withBodies parameter cannot be used for anonymous functions.'); } - $class = $from->isAnonymous() - ? new ClassType - : new ClassType($from->getShortName(), new PhpNamespace($from->getNamespaceName())); - + $enumIface = null; if (PHP_VERSION_ID >= 80100 && $from->isEnum()) { - $class->setType($class::TYPE_ENUM); + $class = new EnumType($from->getShortName(), new PhpNamespace($from->getNamespaceName())); $from = new \ReflectionEnum($from->getName()); $enumIface = $from->isBacked() ? \BackedEnum::class : \UnitEnum::class; + } elseif ($from->isAnonymous()) { + $class = new ClassType; + } elseif ($from->isInterface()) { + $class = new InterfaceType($from->getShortName(), new PhpNamespace($from->getNamespaceName())); + } elseif ($from->isTrait()) { + $class = new TraitType($from->getShortName(), new PhpNamespace($from->getNamespaceName())); } else { - $class->setType($from->isInterface() ? $class::TYPE_INTERFACE : ($from->isTrait() ? $class::TYPE_TRAIT : $class::TYPE_CLASS)); + $class = new ClassType($from->getShortName(), new PhpNamespace($from->getNamespaceName())); $class->setFinal($from->isFinal() && $class->isClass()); $class->setAbstract($from->isAbstract() && $class->isClass()); - $enumIface = null; } $ifaces = $from->getInterfaceNames(); @@ -62,7 +64,7 @@ public function fromClassReflection( if ($from->isInterface()) { $class->setExtends($ifaces); - } else { + } elseif ($ifaces) { $ifaces = array_diff($ifaces, [$enumIface]); $class->setImplements($ifaces); } @@ -87,7 +89,9 @@ public function fromClassReflection( } } - $class->setProperties($props); + if ($props) { + $class->setProperties($props); + } $methods = $resolutions = []; foreach ($from->getMethods() as $method) { @@ -134,7 +138,9 @@ public function fromClassReflection( } $class->setConstants($consts); - $class->setCases($cases); + if ($cases) { + $class->setCases($cases); + } return $class; } @@ -268,7 +274,7 @@ public function fromObject(object $obj): Literal } - public function fromClassCode(string $code): ClassType + public function fromClassCode(string $code): ClassLike { $classes = $this->fromCode($code)->getClasses(); return reset($classes) ?: throw new Nette\InvalidStateException('The code does not contain any class.'); @@ -301,7 +307,7 @@ private function getVisibility($from): string { return $from->isPrivate() ? ClassType::VISIBILITY_PRIVATE - : ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ClassType::VISIBILITY_PUBLIC); + : ($from->isProtected() ? ClassLike::VISIBILITY_PROTECTED : ClassLike::VISIBILITY_PUBLIC); } diff --git a/src/PhpGenerator/InterfaceType.php b/src/PhpGenerator/InterfaceType.php new file mode 100644 index 00000000..5a2d61d3 --- /dev/null +++ b/src/PhpGenerator/InterfaceType.php @@ -0,0 +1,77 @@ +validateNames($names); + $this->extends = $names; + return $this; + } + + + /** @return string[] */ + public function getExtends(): array + { + return $this->extends; + } + + + public function addExtend(string $name): static + { + $this->validateNames([$name]); + $this->extends[] = $name; + return $this; + } + + + public function addMember(Method|Constant $member): static + { + $name = $member->getName(); + [$type, $n] = match (true) { + $member instanceof Constant => ['consts', $name], + $member instanceof Method => ['methods', strtolower($name)], + }; + if (isset($this->$type[$n])) { + throw new Nette\InvalidStateException("Cannot add member '$name', because it already exists."); + } + $this->$type[$n] = $member; + return $this; + } + + + public function __clone() + { + $clone = fn($item) => clone $item; + $this->consts = array_map($clone, $this->consts); + $this->methods = array_map($clone, $this->methods); + } +} diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index 6f7270f2..d552964a 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -44,7 +44,7 @@ public function addClass(string $name): ClassType } - public function addInterface(string $name): ClassType + public function addInterface(string $name): InterfaceType { return $this ->addNamespace(Helpers::extractNamespace($name)) @@ -52,7 +52,7 @@ public function addInterface(string $name): ClassType } - public function addTrait(string $name): ClassType + public function addTrait(string $name): TraitType { return $this ->addNamespace(Helpers::extractNamespace($name)) @@ -60,7 +60,7 @@ public function addTrait(string $name): ClassType } - public function addEnum(string $name): ClassType + public function addEnum(string $name): EnumType { return $this ->addNamespace(Helpers::extractNamespace($name)) @@ -97,7 +97,7 @@ public function getNamespaces(): array } - /** @return ClassType[] */ + /** @return ClassLike[] */ public function getClasses(): array { $classes = []; diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 4afb763e..7a41e97a 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -41,7 +41,7 @@ final class PhpNamespace self::NAME_CONSTANT => [], ]; - /** @var ClassType[] */ + /** @var ClassLike[] */ private array $classes = []; /** @var GlobalFunction[] */ @@ -234,7 +234,7 @@ public function simplifyName(string $name, string $of = self::NAME_NORMAL): stri } - public function add(ClassType $class): static + public function add(ClassLike $class): static { $name = $class->getName(); if ($name === null) { @@ -260,21 +260,24 @@ public function addClass(string $name): ClassType } - public function addInterface(string $name): ClassType + public function addInterface(string $name): InterfaceType { - return $this->addClass($name)->setType(ClassType::TYPE_INTERFACE); + $this->add($iface = new InterfaceType($name, $this)); + return $iface; } - public function addTrait(string $name): ClassType + public function addTrait(string $name): TraitType { - return $this->addClass($name)->setType(ClassType::TYPE_TRAIT); + $this->add($trait = new TraitType($name, $this)); + return $trait; } - public function addEnum(string $name): ClassType + public function addEnum(string $name): EnumType { - return $this->addClass($name)->setType(ClassType::TYPE_ENUM); + $this->add($enum = new EnumType($name, $this)); + return $enum; } @@ -305,7 +308,7 @@ public function removeFunction(string $name): static } - /** @return ClassType[] */ + /** @return ClassLike[] */ public function getClasses(): array { $res = []; diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 192fc92a..5cc2e3f8 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -126,7 +126,7 @@ public function printMethod(Method $method, ?PhpNamespace $namespace = null, boo } - public function printClass(ClassType $class, ?PhpNamespace $namespace = null): string + public function printClass(ClassLike $class, ?PhpNamespace $namespace = null): string { $this->namespace = $this->resolveTypes ? $namespace : null; $class->validate(); @@ -135,22 +135,26 @@ public function printClass(ClassType $class, ?PhpNamespace $namespace = null): s : fn($s) => $s; $traits = []; - foreach ($class->getTraits() as $trait) { - $resolutions = $trait->getResolutions(); - $traits[] = Helpers::formatDocComment((string) $trait->getComment()) - . 'use ' . $resolver($trait->getName()) - . ($resolutions - ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n" - : ";\n"); + if ($class instanceof ClassType || $class instanceof TraitType || $class instanceof EnumType) { + foreach ($class->getTraits() as $trait) { + $resolutions = $trait->getResolutions(); + $traits[] = Helpers::formatDocComment((string) $trait->getComment()) + . 'use ' . $resolver($trait->getName()) + . ($resolutions + ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n" + : ";\n"); + } } $cases = []; - foreach ($class->getCases() as $case) { - $cases[] = Helpers::formatDocComment((string) $case->getComment()) - . self::printAttributes($case->getAttributes()) - . 'case ' . $case->getName() - . ($case->getValue() === null ? '' : ' = ' . $this->dump($case->getValue())) - . ";\n"; + if ($class instanceof EnumType) { + foreach ($class->getCases() as $case) { + $cases[] = Helpers::formatDocComment((string) $case->getComment()) + . self::printAttributes($case->getAttributes()) + . 'case ' . $case->getName() + . ($case->getValue() === null ? '' : ' = ' . $this->dump($case->getValue())) + . ";\n"; + } } $enumType = isset($case) && $case->getValue() !== null @@ -158,40 +162,49 @@ public function printClass(ClassType $class, ?PhpNamespace $namespace = null): s : ''; $consts = []; - foreach ($class->getConstants() as $const) { - $def = ($const->isFinal() ? 'final ' : '') - . ($const->getVisibility() ? $const->getVisibility() . ' ' : '') - . 'const ' . $const->getName() . ' = '; - - $consts[] = Helpers::formatDocComment((string) $const->getComment()) - . self::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->getConstants() as $const) { + $def = ($const->isFinal() ? 'final ' : '') + . ($const->getVisibility() ? $const->getVisibility() . ' ' : '') + . 'const ' . $const->getName() . ' = '; + + $consts[] = Helpers::formatDocComment((string) $const->getComment()) + . self::printAttributes($const->getAttributes()) + . $def + . $this->dump($const->getValue(), strlen($def)) . ";\n"; + } - $properties = []; - foreach ($class->getProperties() as $property) { - $property->validate(); - $type = $property->getType(); - $def = (($property->getVisibility() ?: 'public') - . ($property->isStatic() ? ' static' : '') - . ($property->isReadOnly() && $type ? ' readonly' : '') - . ' ' - . ltrim($this->printType($type, $property->isNullable()) . ' ') - . '$' . $property->getName()); - - $properties[] = Helpers::formatDocComment((string) $property->getComment()) - . self::printAttributes($property->getAttributes()) - . $def - . ($property->getValue() === null && !$property->isInitialized() - ? '' - : ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = ' - . ";\n"; + foreach ($class->getMethods() as $method) { + $methods[] = $this->printMethod($method, $namespace, $class->isInterface()); + } } - $methods = []; - foreach ($class->getMethods() as $method) { - $methods[] = $this->printMethod($method, $namespace, $class->isInterface()); + $properties = []; + if ($class instanceof ClassType || $class instanceof TraitType) { + foreach ($class->getProperties() as $property) { + $property->validate(); + $type = $property->getType(); + $def = (($property->getVisibility() ?: 'public') + . ($property->isStatic() ? ' static' : '') + . ($property->isReadOnly() && $type ? ' readonly' : '') + . ' ' + . ltrim($this->printType($type, $property->isNullable()) . ' ') + . '$' . $property->getName()); + + $properties[] = Helpers::formatDocComment((string) $property->getComment()) + . self::printAttributes($property->getAttributes()) + . $def + . ($property->getValue() === null && !$property->isInitialized() + ? '' + : ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = ' + . ";\n"; + } } $members = array_filter([ @@ -203,14 +216,25 @@ public function printClass(ClassType $class, ?PhpNamespace $namespace = null): s . implode(str_repeat("\n", $this->linesBetweenMethods), $methods), ]); + $type = match (true) { + $class instanceof ClassType => $class->getType(), + $class instanceof InterfaceType => 'interface', + $class instanceof TraitType => 'trait', + $class instanceof EnumType => 'enum', + }; + return Strings::normalize( Helpers::formatDocComment($class->getComment() . "\n") . self::printAttributes($class->getAttributes()) - . ($class->isAbstract() ? 'abstract ' : '') - . ($class->isFinal() ? 'final ' : '') - . ($class->getName() ? $class->getType() . ' ' . $class->getName() . $enumType . ' ' : '') - . ($class->getExtends() ? 'extends ' . implode(', ', array_map($resolver, (array) $class->getExtends())) . ' ' : '') - . ($class->getImplements() ? 'implements ' . implode(', ', array_map($resolver, $class->getImplements())) . ' ' : '') + . ($class instanceof ClassType && $class->isAbstract() ? 'abstract ' : '') + . ($class instanceof ClassType && $class->isFinal() ? 'final ' : '') + . ($class->getName() ? $type . ' ' . $class->getName() . $enumType . ' ' : '') + . (($class instanceof ClassType || $class instanceof InterfaceType) && $class->getExtends() + ? 'extends ' . implode(', ', array_map($resolver, (array) $class->getExtends())) . ' ' + : '') + . (($class instanceof ClassType || $class instanceof EnumType) && $class->getImplements() + ? 'implements ' . implode(', ', array_map($resolver, $class->getImplements())) . ' ' + : '') . ($class->getName() ? "\n" : '') . "{\n" . ($members ? $this->indent(implode("\n", $members)) : '') . '}', diff --git a/src/PhpGenerator/TraitType.php b/src/PhpGenerator/TraitType.php new file mode 100644 index 00000000..57621ad4 --- /dev/null +++ b/src/PhpGenerator/TraitType.php @@ -0,0 +1,53 @@ +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], + }; + if (isset($this->$type[$n])) { + throw new Nette\InvalidStateException("Cannot add member '$name', because it already exists."); + } + $this->$type[$n] = $member; + return $this; + } + + + 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/TraitUse.php b/src/PhpGenerator/TraitUse.php index 128deace..9a9b1bfd 100644 --- a/src/PhpGenerator/TraitUse.php +++ b/src/PhpGenerator/TraitUse.php @@ -25,10 +25,10 @@ final class TraitUse /** @var string[] */ private array $resolutions = []; - private ?ClassType $parent; + private ?ClassLike $parent; - public function __construct(string $name, ?ClassType $parent = null) + public function __construct(string $name, ?ClassLike $parent = null) { if (!Nette\PhpGenerator\Helpers::isNamespaceIdentifier($name, true)) { throw new Nette\InvalidArgumentException("Value '$name' is not valid trait name."); diff --git a/src/PhpGenerator/Traits/ConstantsAware.php b/src/PhpGenerator/Traits/ConstantsAware.php new file mode 100644 index 00000000..f048e355 --- /dev/null +++ b/src/PhpGenerator/Traits/ConstantsAware.php @@ -0,0 +1,65 @@ + */ + private array $consts = []; + + + /** @param Constant[] $consts */ + public function setConstants(array $consts): static + { + $this->consts = []; + foreach ($consts as $k => $const) { + if (!$const instanceof Constant) { + trigger_error(__METHOD__ . '() accepts an array of Constant as parameter, ' . get_debug_type($const) . ' given.', E_USER_DEPRECATED); + $const = (new Constant($k))->setValue($const)->setPublic(); + } + + $this->consts[$const->getName()] = $const; + } + + return $this; + } + + + /** @return Constant[] */ + public function getConstants(): array + { + return $this->consts; + } + + + public function addConstant(string $name, mixed $value): Constant + { + if (isset($this->consts[$name])) { + throw new Nette\InvalidStateException("Cannot add constant '$name', because it already exists."); + } + return $this->consts[$name] = (new Constant($name)) + ->setValue($value) + ->setPublic(); + } + + + public function removeConstant(string $name): static + { + unset($this->consts[$name]); + return $this; + } +} diff --git a/src/PhpGenerator/Traits/MethodsAware.php b/src/PhpGenerator/Traits/MethodsAware.php new file mode 100644 index 00000000..76e50f34 --- /dev/null +++ b/src/PhpGenerator/Traits/MethodsAware.php @@ -0,0 +1,87 @@ + */ + private array $methods = []; + + + /** @param Method[] $methods */ + public function setMethods(array $methods): static + { + (function (Method ...$methods) {})(...$methods); + $this->methods = []; + foreach ($methods as $m) { + $this->methods[strtolower($m->getName())] = $m; + } + + return $this; + } + + + /** @return Method[] */ + public function getMethods(): array + { + $res = []; + foreach ($this->methods as $m) { + $res[$m->getName()] = $m; + } + + return $res; + } + + + public function getMethod(string $name): Method + { + $m = $this->methods[strtolower($name)] ?? null; + if (!$m) { + throw new Nette\InvalidArgumentException("Method '$name' not found."); + } + + return $m; + } + + + public function addMethod(string $name): Method + { + $lower = strtolower($name); + if (isset($this->methods[$lower])) { + throw new Nette\InvalidStateException("Cannot add method '$name', because it already exists."); + } + $method = new Method($name); + if (!$this->isInterface()) { + $method->setPublic(); + } + + return $this->methods[$lower] = $method; + } + + + public function removeMethod(string $name): static + { + unset($this->methods[strtolower($name)]); + return $this; + } + + + public function hasMethod(string $name): bool + { + return isset($this->methods[strtolower($name)]); + } +} diff --git a/src/PhpGenerator/Traits/PropertiesAware.php b/src/PhpGenerator/Traits/PropertiesAware.php new file mode 100644 index 00000000..ead392de --- /dev/null +++ b/src/PhpGenerator/Traits/PropertiesAware.php @@ -0,0 +1,79 @@ + */ + private array $properties = []; + + + /** @param Property[] $props */ + public function setProperties(array $props): static + { + (function (Property ...$props) {})(...$props); + $this->properties = []; + foreach ($props as $v) { + $this->properties[$v->getName()] = $v; + } + + return $this; + } + + + /** @return Property[] */ + public function getProperties(): array + { + return $this->properties; + } + + + public function getProperty(string $name): Property + { + if (!isset($this->properties[$name])) { + throw new Nette\InvalidArgumentException("Property '$name' not found."); + } + + return $this->properties[$name]; + } + + + /** @param string $name without $ */ + public function addProperty(string $name, mixed $value = null): Property + { + if (isset($this->properties[$name])) { + throw new Nette\InvalidStateException("Cannot add property '$name', because it already exists."); + } + return $this->properties[$name] = func_num_args() > 1 + ? (new Property($name))->setValue($value) + : new Property($name); + } + + + /** @param string $name without $ */ + public function removeProperty(string $name): static + { + unset($this->properties[$name]); + return $this; + } + + + public function hasProperty(string $name): bool + { + return isset($this->properties[$name]); + } +} diff --git a/src/PhpGenerator/Traits/TraitsAware.php b/src/PhpGenerator/Traits/TraitsAware.php new file mode 100644 index 00000000..0d549784 --- /dev/null +++ b/src/PhpGenerator/Traits/TraitsAware.php @@ -0,0 +1,69 @@ + */ + private array $traits = []; + + + /** @param TraitUse[] $traits */ + public function setTraits(array $traits): static + { + (function (TraitUse|string ...$traits) {})(...$traits); + $this->traits = []; + foreach ($traits as $trait) { + if (!$trait instanceof TraitUse) { + trigger_error(__METHOD__ . '() accepts an array of TraitUse as parameter, string given.', E_USER_DEPRECATED); + $trait = new TraitUse($trait); + } + + $this->traits[$trait->getName()] = $trait; + } + + return $this; + } + + + /** @return TraitUse[] */ + public function getTraits(): array + { + return $this->traits; + } + + + public function addTrait(string $name, array|bool|null $deprecatedParam = null): TraitUse + { + if (isset($this->traits[$name])) { + throw new Nette\InvalidStateException("Cannot add trait '$name', because it already exists."); + } + $this->traits[$name] = $trait = new TraitUse($name, $this); + if (is_array($deprecatedParam)) { + array_map(fn($item) => $trait->addResolution($item), $deprecatedParam); + } + + return $trait; + } + + + public function removeTrait(string $name): static + { + unset($this->traits[$name]); + return $this; + } +} diff --git a/tests/PhpGenerator/ClassType.enum.phpt b/tests/PhpGenerator/ClassType.enum.phpt index 525f0c40..eefb7a7f 100644 --- a/tests/PhpGenerator/ClassType.enum.phpt +++ b/tests/PhpGenerator/ClassType.enum.phpt @@ -6,14 +6,14 @@ declare(strict_types=1); -use Nette\PhpGenerator\ClassType; +use Nette\PhpGenerator\EnumType; use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$enum = ClassType::enum('Suit'); +$enum = new EnumType('Suit'); Assert::true($enum->isEnum()); @@ -37,7 +37,7 @@ $enum->addCase('Spades'); $res[] = $enum; -$enum = ClassType::enum('Method'); +$enum = new EnumType('Method'); $enum->addImplement('IOne'); $enum->addCase('GET', 'get'); diff --git a/tests/PhpGenerator/ClassType.fromCode.phpt b/tests/PhpGenerator/ClassType.fromCode.phpt index c21ff8d8..794504a3 100644 --- a/tests/PhpGenerator/ClassType.fromCode.phpt +++ b/tests/PhpGenerator/ClassType.fromCode.phpt @@ -2,14 +2,14 @@ declare(strict_types=1); -use Nette\PhpGenerator\ClassType; +use Nette\PhpGenerator\InterfaceType; use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$class = ClassType::fromCode(file_get_contents(__DIR__ . '/fixtures/classes.php')); -Assert::type(ClassType::class, $class); +$class = InterfaceType::fromCode(file_get_contents(__DIR__ . '/fixtures/classes.php')); +Assert::type(InterfaceType::class, $class); Assert::match(<<<'XX' /** * Interface @@ -23,5 +23,5 @@ Assert::match(<<<'XX' Assert::exception(function () { - ClassType::fromCode('addExtend('IOne') ->addExtend('ITwo') diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index 1fd93b7a..1ec06192 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -22,7 +22,7 @@ Assert::false($class->isAbstract()); Assert::true($class->isClass()); Assert::false($class->isInterface()); Assert::false($class->isTrait()); -Assert::same([], $class->getExtends()); +Assert::same(null, $class->getExtends()); Assert::same([], $class->getTraits()); $class diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index bdfc77e3..f0736cba 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -136,9 +136,9 @@ Assert::same( Assert::same( PHP_VERSION_ID < 80100 - ? "\\Closure::fromCallable(['Nette\\PhpGenerator\\ClassType', 'from'])" - : 'Nette\PhpGenerator\ClassType::from(...)', - $dumper->dump(Closure::fromCallable([Nette\PhpGenerator\ClassType::class, 'from'])), + ? "\\Closure::fromCallable(['Nette\\PhpGenerator\\ClassLike', 'from'])" + : 'Nette\PhpGenerator\ClassLike::from(...)', + $dumper->dump(Closure::fromCallable([Nette\PhpGenerator\ClassLike::class, 'from'])), ); Assert::exception(function () { diff --git a/tests/PhpGenerator/Factory.fromClassCode.phpt b/tests/PhpGenerator/Factory.fromClassCode.phpt index fc444db3..8d6b08d5 100644 --- a/tests/PhpGenerator/Factory.fromClassCode.phpt +++ b/tests/PhpGenerator/Factory.fromClassCode.phpt @@ -12,7 +12,7 @@ require __DIR__ . '/../bootstrap.php'; $factory = new Factory; $class = $factory->fromClassCode(file_get_contents(__DIR__ . '/fixtures/classes.php')); -Assert::type(Nette\PhpGenerator\ClassType::class, $class); +Assert::type(Nette\PhpGenerator\InterfaceType::class, $class); Assert::match(<<<'XX' /** * Interface diff --git a/tests/PhpGenerator/PhpFile.phpt b/tests/PhpGenerator/PhpFile.phpt index 457ecc38..804224e4 100644 --- a/tests/PhpGenerator/PhpFile.phpt +++ b/tests/PhpGenerator/PhpFile.phpt @@ -53,7 +53,7 @@ $enumEN = $namespaceBar->addEnum('EN'); Assert::same($enumEN->getNamespace(), $namespaceBar); $classB - ->addExtend('Foo\A') + ->setExtends('Foo\A') ->addImplement('Foo\B') ->addTrait('Foo\C'); diff --git a/tests/PhpGenerator/invalidNames.phpt b/tests/PhpGenerator/invalidNames.phpt index 5830dc48..57b8ff21 100644 --- a/tests/PhpGenerator/invalidNames.phpt +++ b/tests/PhpGenerator/invalidNames.phpt @@ -92,14 +92,6 @@ Assert::exception(function () use ($class) { $class->setExtends('*'); }, Nette\InvalidArgumentException::class, "Value '*' is not valid class name."); -Assert::exception(function () use ($class) { - $class->setExtends(['A', '*']); -}, Nette\InvalidArgumentException::class, "Value '*' is not valid class name."); - -Assert::exception(function () use ($class) { - $class->addExtend('*'); -}, Nette\InvalidArgumentException::class, "Value '*' is not valid class name."); - Assert::exception(function () use ($class) { $class->setImplements(['A', '*']); }, Nette\InvalidArgumentException::class, "Value '*' is not valid class name."); @@ -113,6 +105,16 @@ Assert::exception(function () use ($class) { }, Nette\InvalidArgumentException::class, "Value '*' is not valid trait name."); +$iface = new Nette\PhpGenerator\InterfaceType('Abc'); +Assert::exception(function () use ($iface) { + $iface->setExtends(['A', '*']); +}, Nette\InvalidArgumentException::class, "Value '*' is not valid class name."); + +Assert::exception(function () use ($iface) { + $iface->addExtend('*'); +}, Nette\InvalidArgumentException::class, "Value '*' is not valid class name."); + + Assert::noError(function () { new Nette\PhpGenerator\Property('Iñtërnâtiônàlizætiøn'); }); From a4eb8000ddaed0f2cbb4a40bbdd4137fdf1da578 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 1 Mar 2022 23:10:06 +0100 Subject: [PATCH 115/266] Enum: value can be expression --- src/PhpGenerator/EnumCase.php | 6 +++--- src/PhpGenerator/EnumType.php | 16 +++++++++++++++- src/PhpGenerator/Extractor.php | 8 +++++++- src/PhpGenerator/Printer.php | 11 +++++------ .../PhpGenerator/expected/ClassType.enum.expect | 4 ++-- .../expected/ClassType.from.enum.expect | 6 +++--- .../PhpGenerator/expected/Extractor.enum.expect | 14 +++++++++++--- tests/PhpGenerator/fixtures/enum.php | 7 +++++++ 8 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src/PhpGenerator/EnumCase.php b/src/PhpGenerator/EnumCase.php index eab7ff4b..226fd090 100644 --- a/src/PhpGenerator/EnumCase.php +++ b/src/PhpGenerator/EnumCase.php @@ -22,17 +22,17 @@ final class EnumCase use Traits\CommentAware; use Traits\AttributeAware; - private string|int|null $value = null; + private string|int|Literal|null $value = null; - public function setValue(string|int|null $val): static + public function setValue(string|int|Literal|null $val): static { $this->value = $val; return $this; } - public function getValue(): string|int|null + public function getValue(): string|int|Literal|null { return $this->value; } diff --git a/src/PhpGenerator/EnumType.php b/src/PhpGenerator/EnumType.php index e24a6d39..8122a2ad 100644 --- a/src/PhpGenerator/EnumType.php +++ b/src/PhpGenerator/EnumType.php @@ -26,6 +26,20 @@ final class EnumType extends ClassLike /** @var array */ private array $cases = []; + private ?string $type = null; + + + public function setType(?string $type): static + { + $this->type = $type; + return $this; + } + + + public function getType(): ?string + { + return $this->type; + } /** @@ -85,7 +99,7 @@ public function getCases(): array /** Adds case to enum */ - public function addCase(string $name, string|int|null $value = null): EnumCase + public function addCase(string $name, string|int|Literal|null $value = null): EnumCase { if (isset($this->cases[$name])) { throw new Nette\InvalidStateException("Cannot add cases '$name', because it already exists."); diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index f641276a..d291f47f 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -275,6 +275,7 @@ private function addTraitToFile(PhpFile $phpFile, Node\Stmt\Trait_ $node): Trait private function addEnumToFile(PhpFile $phpFile, Node\Stmt\Enum_ $node): EnumType { $enum = $phpFile->addEnum($node->namespacedName->toString()); + $enum->setType($node->scalarType?->toString()); foreach ($node->implements as $item) { $enum->addImplement($item->toString()); } @@ -362,7 +363,12 @@ private function addConstantToClass(ClassLike $class, Node\Stmt\ClassConst $node private function addEnumCaseToClass(EnumType $class, Node\Stmt\EnumCase $node): void { - $case = $class->addCase($node->name->toString(), $node->expr?->value); + $value = match (true) { + $node->expr === null => null, + $node->expr instanceof Node\Scalar\LNumber, $node->expr instanceof Node\Scalar\String_ => $node->expr->value, + default => new Literal($this->getReformattedContents([$node->expr], 1)), + }; + $case = $class->addCase($node->name->toString(), $value); $this->addCommentAndAttributes($case, $node); } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 5cc2e3f8..5e2d57e5 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -147,8 +147,11 @@ public function printClass(ClassLike $class, ?PhpNamespace $namespace = null): s } $cases = []; + $enumType = null; if ($class instanceof EnumType) { + $enumType = $class->getType(); foreach ($class->getCases() as $case) { + $enumType ??= is_scalar($case->getValue()) ? get_debug_type($case->getValue()) : null; $cases[] = Helpers::formatDocComment((string) $case->getComment()) . self::printAttributes($case->getAttributes()) . 'case ' . $case->getName() @@ -157,10 +160,6 @@ public function printClass(ClassLike $class, ?PhpNamespace $namespace = null): s } } - $enumType = isset($case) && $case->getValue() !== null - ? $this->returnTypeColon . get_debug_type($case->getValue()) - : ''; - $consts = []; $methods = []; if ( @@ -209,8 +208,8 @@ public function printClass(ClassLike $class, ?PhpNamespace $namespace = null): s $members = array_filter([ implode('', $traits), - $this->joinProperties($cases), $this->joinProperties($consts), + $this->joinProperties($cases), $this->joinProperties($properties), ($methods && $properties ? str_repeat("\n", $this->linesBetweenMethods - 1) : '') . implode(str_repeat("\n", $this->linesBetweenMethods), $methods), @@ -228,7 +227,7 @@ public function printClass(ClassLike $class, ?PhpNamespace $namespace = null): s . self::printAttributes($class->getAttributes()) . ($class instanceof ClassType && $class->isAbstract() ? 'abstract ' : '') . ($class instanceof ClassType && $class->isFinal() ? 'final ' : '') - . ($class->getName() ? $type . ' ' . $class->getName() . $enumType . ' ' : '') + . ($class->getName() ? $type . ' ' . $class->getName() . ($enumType ? $this->returnTypeColon . $enumType : '') . ' ' : '') . (($class instanceof ClassType || $class instanceof InterfaceType) && $class->getExtends() ? 'extends ' . implode(', ', array_map($resolver, (array) $class->getExtends())) . ' ' : '') diff --git a/tests/PhpGenerator/expected/ClassType.enum.expect b/tests/PhpGenerator/expected/ClassType.enum.expect index f681f183..d9b74983 100644 --- a/tests/PhpGenerator/expected/ClassType.enum.expect +++ b/tests/PhpGenerator/expected/ClassType.enum.expect @@ -7,6 +7,8 @@ enum Suit { use ObjectTrait; + public const ACTIVE = false; + /** ♣ */ #[ValueAttribute] case Clubs; @@ -16,8 +18,6 @@ enum Suit case Hearts; case Spades; - public const ACTIVE = false; - public function foo() { return 10; diff --git a/tests/PhpGenerator/expected/ClassType.from.enum.expect b/tests/PhpGenerator/expected/ClassType.from.enum.expect index 61ef5732..03264a3b 100644 --- a/tests/PhpGenerator/expected/ClassType.from.enum.expect +++ b/tests/PhpGenerator/expected/ClassType.from.enum.expect @@ -4,6 +4,9 @@ #[\ExampleAttribute] enum Enum1 { + public const FOO = 123; + public const BAR = \Abc\Enum1::Clubs; + /** Commented */ case Clubs; @@ -12,9 +15,6 @@ enum Enum1 case Hearts; case Spades; - public const FOO = 123; - public const BAR = \Abc\Enum1::Clubs; - public function foo($x = self::Diamonds) { } diff --git a/tests/PhpGenerator/expected/Extractor.enum.expect b/tests/PhpGenerator/expected/Extractor.enum.expect index 3f7943a0..f3c8e7b2 100644 --- a/tests/PhpGenerator/expected/Extractor.enum.expect +++ b/tests/PhpGenerator/expected/Extractor.enum.expect @@ -10,6 +10,9 @@ namespace Abc; #[\ExampleAttribute] enum Enum1 { + public const FOO = 123; + public const BAR = self::Clubs; + /** Commented */ case Clubs; @@ -18,9 +21,6 @@ enum Enum1 case Hearts; case Spades; - public const FOO = 123; - public const BAR = self::Clubs; - public function foo($x = self::Diamonds) { } @@ -35,3 +35,11 @@ enum Enum2: string implements \Countable { } } + +enum Enum3: int +{ + public const FOO = 123; + + case A = self::FOO; + case B = 20 + 5; +} diff --git a/tests/PhpGenerator/fixtures/enum.php b/tests/PhpGenerator/fixtures/enum.php index 3128ca2e..7063e245 100644 --- a/tests/PhpGenerator/fixtures/enum.php +++ b/tests/PhpGenerator/fixtures/enum.php @@ -35,3 +35,10 @@ function count(): int { } } + +enum Enum3: int +{ + const FOO = 123; + case A = self::FOO; + case B = 20 + 5; +} From ab55fb847f98a40c6cd8ffa040e4e6354eeb22b3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 7 Mar 2022 17:32:04 +0100 Subject: [PATCH 116/266] constants are PascalCase --- readme.md | 4 +- src/PhpGenerator/ClassLike.php | 14 ++++-- src/PhpGenerator/ClassType.php | 1 - src/PhpGenerator/Dumper.php | 4 +- src/PhpGenerator/Extractor.php | 12 ++--- src/PhpGenerator/Factory.php | 4 +- src/PhpGenerator/Helpers.php | 17 ++++--- src/PhpGenerator/Method.php | 2 +- src/PhpGenerator/PhpFile.php | 2 +- src/PhpGenerator/PhpNamespace.php | 50 +++++++++++-------- src/PhpGenerator/Printer.php | 12 ++--- src/PhpGenerator/Traits/VisibilityAware.php | 16 +++--- tests/PhpGenerator/PhpNamespace.aliases.phpt | 4 +- tests/PhpGenerator/PhpNamespace.resolve.phpt | 34 ++++++------- tests/PhpGenerator/PhpNamespace.simplify.phpt | 34 ++++++------- 15 files changed, 113 insertions(+), 97 deletions(-) diff --git a/readme.md b/readme.md index 3784c0e1..2a75b705 100644 --- a/readme.md +++ b/readme.md @@ -590,14 +590,14 @@ To simplify a fully qualified class, function or constant name according to the ```php echo $namespace->simplifyName('Foo\Bar'); // 'Bar', because 'Foo' is current namespace -echo $namespace->simplifyName('iter\range', $namespace::NAME_FUNCTION); // 'range', because of the defined use-statement +echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', because of the defined use-statement ``` Conversely, you can convert a simplified class, function or constant name to a fully qualified one using the `resolveName` method: ```php echo $namespace->resolveName('Bar'); // 'Foo\Bar' -echo $namespace->resolveName('range', $namespace::NAME_FUNCTION); // 'iter\range' +echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` Class Names Resolving diff --git a/src/PhpGenerator/ClassLike.php b/src/PhpGenerator/ClassLike.php index e1e50bd0..8df107dc 100644 --- a/src/PhpGenerator/ClassLike.php +++ b/src/PhpGenerator/ClassLike.php @@ -22,9 +22,15 @@ abstract class ClassLike use Traits\AttributeAware; public const - VISIBILITY_PUBLIC = 'public', - VISIBILITY_PROTECTED = 'protected', - VISIBILITY_PRIVATE = 'private'; + VisibilityPublic = 'public', + VisibilityProtected = 'protected', + VisibilityPrivate = 'private'; + + /** @deprecated */ + public const + VISIBILITY_PUBLIC = self::VisibilityPublic, + VISIBILITY_PROTECTED = self::VisibilityProtected, + VISIBILITY_PRIVATE = self::VisibilityPrivate; private ?PhpNamespace $namespace; private ?string $name; @@ -78,7 +84,7 @@ public function getNamespace(): ?PhpNamespace public function setName(?string $name): static { - if ($name !== null && (!Helpers::isIdentifier($name) || isset(Helpers::KEYWORDS[strtolower($name)]))) { + if ($name !== null && (!Helpers::isIdentifier($name) || isset(Helpers::Keywords[strtolower($name)]))) { throw new Nette\InvalidArgumentException("Value '$name' is not valid class name."); } diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index bc240800..a78a1d8b 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -33,7 +33,6 @@ final class ClassType extends ClassLike TYPE_ENUM = 'enum'; private string $type = self::TYPE_CLASS; - private bool $final = false; private bool $abstract = false; private ?string $extends = null; diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index d545de64..6f9538e1 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -17,7 +17,7 @@ */ final class Dumper { - private const INDENT_LENGTH = 4; + private const IndentLength = 4; public int $maxDepth = 50; public int $wrapLength = 120; @@ -130,7 +130,7 @@ private function dumpArray(array &$var, array $parents, int $level, int $column) } array_pop($parents); - $wrap = str_contains($outInline, "\n") || $level * self::INDENT_LENGTH + $column + strlen($outInline) + 3 > $this->wrapLength; // 3 = [], + $wrap = str_contains($outInline, "\n") || $level * self::IndentLength + $column + strlen($outInline) + 3 > $this->wrapLength; // 3 = [], return '[' . ($wrap ? $outWrapped : $outInline) . ']'; } diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index d291f47f..b6bd9323 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -115,9 +115,9 @@ private function prepareReplacements(array $nodes): array if ($node instanceof Node\Name\FullyQualified) { if ($node->getAttribute('originalName') instanceof Node\Name) { $of = match (true) { - $node->getAttribute('parent') instanceof Node\Expr\ConstFetch => PhpNamespace::NAME_CONSTANT, - $node->getAttribute('parent') instanceof Node\Expr\FuncCall => PhpNamespace::NAME_FUNCTION, - default => PhpNamespace::NAME_NORMAL, + $node->getAttribute('parent') instanceof Node\Expr\ConstFetch => PhpNamespace::NameConstant, + $node->getAttribute('parent') instanceof Node\Expr\FuncCall => PhpNamespace::NameFunction, + default => PhpNamespace::NameNormal, }; $replacements[] = [ $node->getStartFilePos() - $start, @@ -224,9 +224,9 @@ public function enterNode(Node $node) private function addUseToNamespace(Node\Stmt\Use_ $node, PhpNamespace $namespace): void { $of = [ - $node::TYPE_NORMAL => PhpNamespace::NAME_NORMAL, - $node::TYPE_FUNCTION => PhpNamespace::NAME_FUNCTION, - $node::TYPE_CONSTANT => PhpNamespace::NAME_CONSTANT, + $node::TYPE_NORMAL => PhpNamespace::NameNormal, + $node::TYPE_FUNCTION => PhpNamespace::NameFunction, + $node::TYPE_CONSTANT => PhpNamespace::NameConstant, ][$node->type]; foreach ($node->uses as $use) { $namespace->addUse($use->name->toString(), $use->alias?->toString(), $of); diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index b816c604..34cd8caa 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -306,8 +306,8 @@ private function getAttributes($from): array private function getVisibility($from): string { return $from->isPrivate() - ? ClassType::VISIBILITY_PRIVATE - : ($from->isProtected() ? ClassLike::VISIBILITY_PROTECTED : ClassLike::VISIBILITY_PUBLIC); + ? ClassLike::VisibilityPrivate + : ($from->isProtected() ? ClassLike::VisibilityProtected : ClassLike::VisibilityPublic); } diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 909818c2..1aaaac15 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -19,9 +19,9 @@ final class Helpers { use Nette\StaticClass; - public const PHP_IDENT = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'; + public const ReIdentifier = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'; - public const KEYWORDS = [ + public const Keywords = [ // built-in types 'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1, 'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, 'mixed' => 1, 'false' => 1, @@ -47,6 +47,11 @@ final class Helpers 'true' => 1, ]; + /** @deprecated */ + public const + PHP_IDENT = self::ReIdentifier, + KEYWORDS = self::Keywords; + /** @deprecated use (new Nette\PhpGenerator\Dumper)->dump() */ public static function dump(mixed $var): string @@ -86,9 +91,9 @@ public static function formatDocComment(string $content): string } - public static function tagName(string $name, string $of = PhpNamespace::NAME_NORMAL): string + public static function tagName(string $name, string $of = PhpNamespace::NameNormal): string { - return isset(self::KEYWORDS[strtolower($name)]) + return isset(self::Keywords[strtolower($name)]) ? $name : "/*($of*/$name"; } @@ -121,13 +126,13 @@ public static function unindent(string $s, int $level = 1): string public static function isIdentifier(mixed $value): bool { - return is_string($value) && preg_match('#^' . self::PHP_IDENT . '$#D', $value); + return is_string($value) && preg_match('#^' . self::ReIdentifier . '$#D', $value); } public static function isNamespaceIdentifier(mixed $value, bool $allowLeadingSlash = false): bool { - $re = '#^' . ($allowLeadingSlash ? '\\\\?' : '') . self::PHP_IDENT . '(\\\\' . self::PHP_IDENT . ')*$#D'; + $re = '#^' . ($allowLeadingSlash ? '\\\\?' : '') . self::ReIdentifier . '(\\\\' . self::ReIdentifier . ')*$#D'; return is_string($value) && preg_match($re, $value); } diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index d729047c..0596658c 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -99,7 +99,7 @@ public function addPromotedParameter(string $name, mixed $defaultValue = null): /** @throws Nette\InvalidStateException */ public function validate(): void { - if ($this->abstract && ($this->final || $this->visibility === ClassType::VISIBILITY_PRIVATE)) { + if ($this->abstract && ($this->final || $this->visibility === ClassLike::VisibilityPrivate)) { throw new Nette\InvalidStateException("Method $this->name() cannot be abstract and final or private at the same time."); } } diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index d552964a..68fd89bc 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -127,7 +127,7 @@ public function getFunctions(): array } - public function addUse(string $name, ?string $alias = null, string $of = PhpNamespace::NAME_NORMAL): static + public function addUse(string $name, ?string $alias = null, string $of = PhpNamespace::NameNormal): static { $this->addNamespace('')->addUse($name, $alias, $of); return $this; diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 7a41e97a..caf03cce 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -26,9 +26,15 @@ final class PhpNamespace use Nette\SmartObject; public const - NAME_NORMAL = 'n', - NAME_FUNCTION = 'f', - NAME_CONSTANT = 'c'; + NameNormal = 'n', + NameFunction = 'f', + NameConstant = 'c'; + + /** @deprecated */ + public const + NAME_NORMAL = self::NameNormal, + NAME_FUNCTION = self::NameFunction, + NAME_CONSTANT = self::NameConstant; private string $name; @@ -36,9 +42,9 @@ final class PhpNamespace /** @var string[][] */ private array $aliases = [ - self::NAME_NORMAL => [], - self::NAME_FUNCTION => [], - self::NAME_CONSTANT => [], + self::NameNormal => [], + self::NameFunction => [], + self::NameConstant => [], ]; /** @var ClassLike[] */ @@ -91,21 +97,21 @@ public function getBracketedSyntax(): bool /** * @throws InvalidStateException */ - public function addUse(string $name, ?string $alias = null, string $of = self::NAME_NORMAL): static + public function addUse(string $name, ?string $alias = null, string $of = self::NameNormal): static { if ( !Helpers::isNamespaceIdentifier($name, true) - || (Helpers::isIdentifier($name) && isset(Helpers::KEYWORDS[strtolower($name)])) + || (Helpers::isIdentifier($name) && isset(Helpers::Keywords[strtolower($name)])) ) { throw new Nette\InvalidArgumentException("Value '$name' is not valid class/function/constant name."); - } elseif ($alias && (!Helpers::isIdentifier($alias) || isset(Helpers::KEYWORDS[strtolower($alias)]))) { + } elseif ($alias && (!Helpers::isIdentifier($alias) || isset(Helpers::Keywords[strtolower($alias)]))) { throw new Nette\InvalidArgumentException("Value '$alias' is not valid alias."); } $name = ltrim($name, '\\'); $aliases = array_change_key_case($this->aliases[$of]); - $used = [self::NAME_NORMAL => $this->classes, self::NAME_FUNCTION => $this->functions, self::NAME_CONSTANT => []][$of]; + $used = [self::NameNormal => $this->classes, self::NameFunction => $this->functions, self::NameConstant => []][$of]; if ($alias === null) { $base = Helpers::extractShortName($name); @@ -133,18 +139,18 @@ public function addUse(string $name, ?string $alias = null, string $of = self::N public function addUseFunction(string $name, ?string $alias = null): static { - return $this->addUse($name, $alias, self::NAME_FUNCTION); + return $this->addUse($name, $alias, self::NameFunction); } public function addUseConstant(string $name, ?string $alias = null): static { - return $this->addUse($name, $alias, self::NAME_CONSTANT); + return $this->addUse($name, $alias, self::NameConstant); } /** @return string[] */ - public function getUses(string $of = self::NAME_NORMAL): array + public function getUses(string $of = self::NameNormal): array { asort($this->aliases[$of]); return array_filter( @@ -163,16 +169,16 @@ public function unresolveName(string $name): string } - public function resolveName(string $name, string $of = self::NAME_NORMAL): string + public function resolveName(string $name, string $of = self::NameNormal): string { - if (isset(Helpers::KEYWORDS[strtolower($name)]) || $name === '') { + if (isset(Helpers::Keywords[strtolower($name)]) || $name === '') { return $name; } elseif ($name[0] === '\\') { return substr($name, 1); } $aliases = array_change_key_case($this->aliases[$of]); - if ($of !== self::NAME_NORMAL) { + if ($of !== self::NameNormal) { return $aliases[strtolower($name)] ?? $this->resolveName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name); } @@ -184,21 +190,21 @@ public function resolveName(string $name, string $of = self::NAME_NORMAL): strin } - public function simplifyType(string $type, string $of = self::NAME_NORMAL): string + public function simplifyType(string $type, string $of = self::NameNormal): string { return preg_replace_callback('~[\w\x7f-\xff\\\\]+~', fn($m) => $this->simplifyName($m[0], $of), $type); } - public function simplifyName(string $name, string $of = self::NAME_NORMAL): string + public function simplifyName(string $name, string $of = self::NameNormal): string { - if (isset(Helpers::KEYWORDS[strtolower($name)]) || $name === '') { + if (isset(Helpers::Keywords[strtolower($name)]) || $name === '') { return $name; } $name = ltrim($name, '\\'); - if ($of !== self::NAME_NORMAL) { + if ($of !== self::NameNormal) { foreach ($this->aliases[$of] as $alias => $original) { if (strcasecmp($original, $name) === 0) { return $alias; @@ -244,7 +250,7 @@ public function add(ClassLike $class): static $lower = strtolower($name); if (isset($this->classes[$lower]) && $this->classes[$lower] !== $class) { throw new Nette\InvalidStateException("Cannot add '$name', because it already exists."); - } elseif ($orig = array_change_key_case($this->aliases[self::NAME_NORMAL])[$lower] ?? null) { + } elseif ($orig = array_change_key_case($this->aliases[self::NameNormal])[$lower] ?? null) { throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig."); } @@ -293,7 +299,7 @@ public function addFunction(string $name): GlobalFunction $lower = strtolower($name); if (isset($this->functions[$lower])) { throw new Nette\InvalidStateException("Cannot add '$name', because it already exists."); - } elseif ($orig = array_change_key_case($this->aliases[self::NAME_FUNCTION])[$lower] ?? null) { + } elseif ($orig = array_change_key_case($this->aliases[self::NameFunction])[$lower] ?? null) { throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig."); } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 5e2d57e5..983dde90 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -246,8 +246,8 @@ public function printNamespace(PhpNamespace $namespace): string $this->namespace = $this->resolveTypes ? $namespace : null; $name = $namespace->getName(); $uses = $this->printUses($namespace) - . $this->printUses($namespace, PhpNamespace::NAME_FUNCTION) - . $this->printUses($namespace, PhpNamespace::NAME_CONSTANT); + . $this->printUses($namespace, PhpNamespace::NameFunction) + . $this->printUses($namespace, PhpNamespace::NameConstant); $items = []; foreach ($namespace->getClasses() as $class) { @@ -290,12 +290,12 @@ public function printFile(PhpFile $file): string } - protected function printUses(PhpNamespace $namespace, string $of = PhpNamespace::NAME_NORMAL): string + protected function printUses(PhpNamespace $namespace, string $of = PhpNamespace::NameNormal): string { $prefix = [ - PhpNamespace::NAME_NORMAL => '', - PhpNamespace::NAME_FUNCTION => 'function ', - PhpNamespace::NAME_CONSTANT => 'const ', + PhpNamespace::NameNormal => '', + PhpNamespace::NameFunction => 'function ', + PhpNamespace::NameConstant => 'const ', ][$of]; $name = $namespace->getName(); $uses = []; diff --git a/src/PhpGenerator/Traits/VisibilityAware.php b/src/PhpGenerator/Traits/VisibilityAware.php index 5831efeb..0b3230ed 100644 --- a/src/PhpGenerator/Traits/VisibilityAware.php +++ b/src/PhpGenerator/Traits/VisibilityAware.php @@ -10,7 +10,7 @@ namespace Nette\PhpGenerator\Traits; use Nette; -use Nette\PhpGenerator\ClassType; +use Nette\PhpGenerator\ClassLike; /** @@ -27,7 +27,7 @@ trait VisibilityAware */ public function setVisibility(?string $val): static { - if (!in_array($val, [ClassType::VISIBILITY_PUBLIC, ClassType::VISIBILITY_PROTECTED, ClassType::VISIBILITY_PRIVATE, null], true)) { + if (!in_array($val, [ClassLike::VisibilityPublic, ClassLike::VisibilityProtected, ClassLike::VisibilityPrivate, null], true)) { throw new Nette\InvalidArgumentException('Argument must be public|protected|private.'); } @@ -44,39 +44,39 @@ public function getVisibility(): ?string public function setPublic(): static { - $this->visibility = ClassType::VISIBILITY_PUBLIC; + $this->visibility = ClassLike::VisibilityPublic; return $this; } public function isPublic(): bool { - return $this->visibility === ClassType::VISIBILITY_PUBLIC || $this->visibility === null; + return $this->visibility === ClassLike::VisibilityPublic || $this->visibility === null; } public function setProtected(): static { - $this->visibility = ClassType::VISIBILITY_PROTECTED; + $this->visibility = ClassLike::VisibilityProtected; return $this; } public function isProtected(): bool { - return $this->visibility === ClassType::VISIBILITY_PROTECTED; + return $this->visibility === ClassLike::VisibilityProtected; } public function setPrivate(): static { - $this->visibility = ClassType::VISIBILITY_PRIVATE; + $this->visibility = ClassLike::VisibilityPrivate; return $this; } public function isPrivate(): bool { - return $this->visibility === ClassType::VISIBILITY_PRIVATE; + return $this->visibility === ClassLike::VisibilityPrivate; } } diff --git a/tests/PhpGenerator/PhpNamespace.aliases.phpt b/tests/PhpGenerator/PhpNamespace.aliases.phpt index 1e217f83..9e5281e6 100644 --- a/tests/PhpGenerator/PhpNamespace.aliases.phpt +++ b/tests/PhpGenerator/PhpNamespace.aliases.phpt @@ -48,7 +48,7 @@ Assert::exception(function () use ($namespace) { }, Nette\InvalidStateException::class, "Name 'F2' used already for 'Foo\\f2'."); Assert::same(['C' => 'Bar\C'], $namespace->getUses()); -Assert::same(['f1' => 'Bar\f1'], $namespace->getUses($namespace::NAME_FUNCTION)); +Assert::same(['f1' => 'Bar\f1'], $namespace->getUses($namespace::NameFunction)); // alias generation @@ -90,4 +90,4 @@ Assert::same('Qux', $namespace->simplifyName('Foo\Bar\Baz\Qux')); $namespace = new PhpNamespace('Foo'); $namespace->addUseFunction('Bar\c'); $namespace->addUseFunction('c'); -Assert::same('c1', $namespace->simplifyName('c', $namespace::NAME_FUNCTION)); +Assert::same('c1', $namespace->simplifyName('c', $namespace::NameFunction)); diff --git a/tests/PhpGenerator/PhpNamespace.resolve.phpt b/tests/PhpGenerator/PhpNamespace.resolve.phpt index e5b78f1e..50ec9ffe 100644 --- a/tests/PhpGenerator/PhpNamespace.resolve.phpt +++ b/tests/PhpGenerator/PhpNamespace.resolve.phpt @@ -26,21 +26,21 @@ Assert::same('Bar\C\D', $namespace->resolveName('C\D')); $namespace->addUseFunction('Foo\a'); -Assert::same('bar\c', $namespace->resolveName('bar\c', $namespace::NAME_FUNCTION)); -Assert::same('Foo\a', $namespace->resolveName('A', $namespace::NAME_FUNCTION)); -Assert::same('foo\a\b', $namespace->resolveName('foo\a\b', $namespace::NAME_FUNCTION)); +Assert::same('bar\c', $namespace->resolveName('bar\c', $namespace::NameFunction)); +Assert::same('Foo\a', $namespace->resolveName('A', $namespace::NameFunction)); +Assert::same('foo\a\b', $namespace->resolveName('foo\a\b', $namespace::NameFunction)); $namespace->addUseFunction('Bar\c'); -Assert::same('Bar', $namespace->resolveName('Bar', $namespace::NAME_FUNCTION)); -Assert::same('Bar\c', $namespace->resolveName('C', $namespace::NAME_FUNCTION)); -Assert::same('Bar\C\d', $namespace->resolveName('c\d', $namespace::NAME_FUNCTION)); +Assert::same('Bar', $namespace->resolveName('Bar', $namespace::NameFunction)); +Assert::same('Bar\c', $namespace->resolveName('C', $namespace::NameFunction)); +Assert::same('Bar\C\d', $namespace->resolveName('c\d', $namespace::NameFunction)); $namespace->addUseConstant('Bar\c'); -Assert::same('Bar', $namespace->resolveName('Bar', $namespace::NAME_CONSTANT)); -Assert::same('Bar\c', $namespace->resolveName('C', $namespace::NAME_CONSTANT)); -Assert::same('Bar\C\d', $namespace->resolveName('c\d', $namespace::NAME_CONSTANT)); +Assert::same('Bar', $namespace->resolveName('Bar', $namespace::NameConstant)); +Assert::same('Bar\c', $namespace->resolveName('C', $namespace::NameConstant)); +Assert::same('Bar\C\d', $namespace->resolveName('c\d', $namespace::NameConstant)); @@ -71,16 +71,16 @@ Assert::same('Bar\C\D', $namespace->resolveName('c\D')); $namespace->addUseFunction('Foo\a'); foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { - Assert::same($type, $namespace->resolveName($type, $namespace::NAME_FUNCTION)); + Assert::same($type, $namespace->resolveName($type, $namespace::NameFunction)); } -Assert::same('bar\c', $namespace->resolveName('\bar\c', $namespace::NAME_FUNCTION)); -Assert::same('Foo\a', $namespace->resolveName('A', $namespace::NAME_FUNCTION)); -Assert::same('Foo\C\b', $namespace->resolveName('foo\C\b', $namespace::NAME_FUNCTION)); -Assert::same('Foo\A\b', $namespace->resolveName('A\b', $namespace::NAME_FUNCTION)); +Assert::same('bar\c', $namespace->resolveName('\bar\c', $namespace::NameFunction)); +Assert::same('Foo\a', $namespace->resolveName('A', $namespace::NameFunction)); +Assert::same('Foo\C\b', $namespace->resolveName('foo\C\b', $namespace::NameFunction)); +Assert::same('Foo\A\b', $namespace->resolveName('A\b', $namespace::NameFunction)); $namespace->addUseFunction('Bar\c'); -Assert::same('Bar', $namespace->resolveName('\Bar', $namespace::NAME_FUNCTION)); -Assert::same('Bar\c', $namespace->resolveName('C', $namespace::NAME_FUNCTION)); -Assert::same('Bar\C\d', $namespace->resolveName('c\d', $namespace::NAME_FUNCTION)); +Assert::same('Bar', $namespace->resolveName('\Bar', $namespace::NameFunction)); +Assert::same('Bar\c', $namespace->resolveName('C', $namespace::NameFunction)); +Assert::same('Bar\C\d', $namespace->resolveName('c\d', $namespace::NameFunction)); diff --git a/tests/PhpGenerator/PhpNamespace.simplify.phpt b/tests/PhpGenerator/PhpNamespace.simplify.phpt index ca978a5a..a093cae8 100644 --- a/tests/PhpGenerator/PhpNamespace.simplify.phpt +++ b/tests/PhpGenerator/PhpNamespace.simplify.phpt @@ -23,21 +23,21 @@ Assert::same('C\D', $namespace->simplifyName('Bar\C\D')); $namespace->addUseFunction('Foo\a'); -Assert::same('bar\c', $namespace->simplifyName('bar\c', $namespace::NAME_FUNCTION)); -Assert::same('a', $namespace->simplifyName('foo\A', $namespace::NAME_FUNCTION)); -Assert::same('foo\a\b', $namespace->simplifyName('foo\a\b', $namespace::NAME_FUNCTION)); +Assert::same('bar\c', $namespace->simplifyName('bar\c', $namespace::NameFunction)); +Assert::same('a', $namespace->simplifyName('foo\A', $namespace::NameFunction)); +Assert::same('foo\a\b', $namespace->simplifyName('foo\a\b', $namespace::NameFunction)); $namespace->addUseFunction('Bar\c'); -Assert::same('Bar', $namespace->simplifyName('Bar', $namespace::NAME_FUNCTION)); -Assert::same('c', $namespace->simplifyName('bar\c', $namespace::NAME_FUNCTION)); -Assert::same('C\d', $namespace->simplifyName('Bar\C\d', $namespace::NAME_FUNCTION)); +Assert::same('Bar', $namespace->simplifyName('Bar', $namespace::NameFunction)); +Assert::same('c', $namespace->simplifyName('bar\c', $namespace::NameFunction)); +Assert::same('C\d', $namespace->simplifyName('Bar\C\d', $namespace::NameFunction)); $namespace->addUseConstant('Bar\c'); -Assert::same('Bar', $namespace->simplifyName('Bar', $namespace::NAME_CONSTANT)); -Assert::same('c', $namespace->simplifyName('bar\c', $namespace::NAME_CONSTANT)); -Assert::same('C\d', $namespace->simplifyName('Bar\C\d', $namespace::NAME_CONSTANT)); +Assert::same('Bar', $namespace->simplifyName('Bar', $namespace::NameConstant)); +Assert::same('c', $namespace->simplifyName('bar\c', $namespace::NameConstant)); +Assert::same('C\d', $namespace->simplifyName('Bar\C\d', $namespace::NameConstant)); @@ -75,16 +75,16 @@ Assert::same('žluťoučký', $namespace->simplifyType('foo\žluťoučký')); $namespace->addUseFunction('Foo\a'); foreach (['String', 'string', 'int', 'float', 'bool', 'array', 'callable', 'self', 'parent', ''] as $type) { - Assert::same($type, $namespace->simplifyName($type, $namespace::NAME_FUNCTION)); + Assert::same($type, $namespace->simplifyName($type, $namespace::NameFunction)); } -Assert::same('\bar\c', $namespace->simplifyName('bar\c', $namespace::NAME_FUNCTION)); -Assert::same('a', $namespace->simplifyName('foo\A', $namespace::NAME_FUNCTION)); -Assert::same('Foo\C\b', $namespace->simplifyName('foo\C\b', $namespace::NAME_FUNCTION)); -Assert::same('a\b', $namespace->simplifyName('foo\a\b', $namespace::NAME_FUNCTION)); +Assert::same('\bar\c', $namespace->simplifyName('bar\c', $namespace::NameFunction)); +Assert::same('a', $namespace->simplifyName('foo\A', $namespace::NameFunction)); +Assert::same('Foo\C\b', $namespace->simplifyName('foo\C\b', $namespace::NameFunction)); +Assert::same('a\b', $namespace->simplifyName('foo\a\b', $namespace::NameFunction)); $namespace->addUseFunction('Bar\c'); -Assert::same('\Bar', $namespace->simplifyName('Bar', $namespace::NAME_FUNCTION)); -Assert::same('c', $namespace->simplifyName('bar\c', $namespace::NAME_FUNCTION)); -Assert::same('C\d', $namespace->simplifyName('Bar\C\d', $namespace::NAME_FUNCTION)); +Assert::same('\Bar', $namespace->simplifyName('Bar', $namespace::NameFunction)); +Assert::same('c', $namespace->simplifyName('bar\c', $namespace::NameFunction)); +Assert::same('C\d', $namespace->simplifyName('Bar\C\d', $namespace::NameFunction)); From 010e108616d1823992d4cdc9dcdaa61c2aff75f5 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 9 Mar 2022 22:09:00 +0100 Subject: [PATCH 117/266] PhpNamespace: added removeUse() [Closes #102] --- src/PhpGenerator/PhpNamespace.php | 10 ++++++++++ tests/PhpGenerator/PhpNamespace.aliases.phpt | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index caf03cce..890fc8bf 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -137,6 +137,16 @@ public function addUse(string $name, ?string $alias = null, string $of = self::N } + public function removeUse(string $name, string $of = self::NameNormal): void + { + foreach ($this->aliases[$of] as $alias => $item) { + if (strcasecmp($item, $name) === 0) { + unset($this->aliases[$of][$alias]); + } + } + } + + public function addUseFunction(string $name, ?string $alias = null): static { return $this->addUse($name, $alias, self::NameFunction); diff --git a/tests/PhpGenerator/PhpNamespace.aliases.phpt b/tests/PhpGenerator/PhpNamespace.aliases.phpt index 9e5281e6..5f8dc034 100644 --- a/tests/PhpGenerator/PhpNamespace.aliases.phpt +++ b/tests/PhpGenerator/PhpNamespace.aliases.phpt @@ -57,6 +57,8 @@ $namespace->addUse('C'); Assert::same('C', $namespace->simplifyName('C')); $namespace->addUse('Bar\C'); Assert::same('C1', $namespace->simplifyName('Bar\C')); +$namespace->removeUse('bar\c'); +Assert::same('Bar\C', $namespace->simplifyName('Bar\C')); $namespace = new PhpNamespace(''); $namespace->addUse('Bar\C'); @@ -91,3 +93,5 @@ $namespace = new PhpNamespace('Foo'); $namespace->addUseFunction('Bar\c'); $namespace->addUseFunction('c'); Assert::same('c1', $namespace->simplifyName('c', $namespace::NameFunction)); +$namespace->removeUse('c', $namespace::NameFunction); +Assert::same('\c', $namespace->simplifyName('c', $namespace::NameFunction)); From e4e32ef41a05e8c494bce6f3f8895873a32a68fe Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 9 Mar 2022 22:28:08 +0100 Subject: [PATCH 118/266] Extractor: maintains exact visibility --- src/PhpGenerator/Extractor.php | 32 ++++++++----------- tests/PhpGenerator/ClassType.fromCode.phpt | 2 +- tests/PhpGenerator/Factory.fromClassCode.phpt | 2 +- .../expected/Extractor.bodies.expect | 16 +++++----- .../Extractor.bodies.resolving.expect | 16 +++++----- .../Extractor.bodies.unresolving.expect | 16 +++++----- .../expected/Extractor.classes.80.expect | 4 +-- .../expected/Extractor.classes.expect | 4 +-- .../expected/Extractor.enum.expect | 8 ++--- 9 files changed, 48 insertions(+), 52 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index b6bd9323..8fc98d32 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -311,12 +311,7 @@ private function addPropertyToClass(ClassLike $class, Node\Stmt\Property $node): foreach ($node->props as $item) { $prop = $class->addProperty($item->name->toString()); $prop->setStatic($node->isStatic()); - if ($node->isPrivate()) { - $prop->setPrivate(); - } elseif ($node->isProtected()) { - $prop->setProtected(); - } - + $prop->setVisibility($this->toVisibility($node->flags)); $prop->setType($node->type ? $this->toPhp($node->type) : null); if ($item->default) { $prop->setValue(new Literal($this->getReformattedContents([$item->default], 1))); @@ -334,12 +329,7 @@ private function addMethodToClass(ClassLike $class, Node\Stmt\ClassMethod $node) $method->setAbstract($node->isAbstract()); $method->setFinal($node->isFinal()); $method->setStatic($node->isStatic()); - if ($node->isPrivate()) { - $method->setPrivate(); - } elseif ($node->isProtected()) { - $method->setProtected(); - } - + $method->setVisibility($this->toVisibility($node->flags)); $this->setupFunction($method, $node); } @@ -349,12 +339,7 @@ private function addConstantToClass(ClassLike $class, Node\Stmt\ClassConst $node foreach ($node->consts as $item) { $value = $this->getReformattedContents([$item->value], 1); $const = $class->addConstant($item->name->toString(), new Literal($value)); - if ($node->isPrivate()) { - $const->setPrivate(); - } elseif ($node->isProtected()) { - $const->setProtected(); - } - + $const->setVisibility($this->toVisibility($node->flags)); $const->setFinal(method_exists($node, 'isFinal') && $node->isFinal()); $this->addCommentAndAttributes($const, $node); } @@ -423,6 +408,17 @@ private function setupFunction(GlobalFunction|Method $function, Node\FunctionLik } + private function toVisibility(int $flags): ?string + { + return match (true) { + (bool) ($flags & Node\Stmt\Class_::MODIFIER_PUBLIC) => ClassType::VisibilityPublic, + (bool) ($flags & Node\Stmt\Class_::MODIFIER_PROTECTED) => ClassType::VisibilityProtected, + (bool) ($flags & Node\Stmt\Class_::MODIFIER_PRIVATE) => ClassType::VisibilityPrivate, + default => null, + }; + } + + private function toPhp(mixed $value): string { return $this->printer->prettyPrint([$value]); diff --git a/tests/PhpGenerator/ClassType.fromCode.phpt b/tests/PhpGenerator/ClassType.fromCode.phpt index 794504a3..d12262da 100644 --- a/tests/PhpGenerator/ClassType.fromCode.phpt +++ b/tests/PhpGenerator/ClassType.fromCode.phpt @@ -17,7 +17,7 @@ Assert::match(<<<'XX' */ interface Interface1 { - function func1(); + public function func1(); } XX, (string) $class); diff --git a/tests/PhpGenerator/Factory.fromClassCode.phpt b/tests/PhpGenerator/Factory.fromClassCode.phpt index 8d6b08d5..373256c4 100644 --- a/tests/PhpGenerator/Factory.fromClassCode.phpt +++ b/tests/PhpGenerator/Factory.fromClassCode.phpt @@ -20,6 +20,6 @@ Assert::match(<<<'XX' */ interface Interface1 { - function func1(); + public function func1(); } XX, (string) $class); diff --git a/tests/PhpGenerator/expected/Extractor.bodies.expect b/tests/PhpGenerator/expected/Extractor.bodies.expect index aaa0a3ff..21d22313 100644 --- a/tests/PhpGenerator/expected/Extractor.bodies.expect +++ b/tests/PhpGenerator/expected/Extractor.bodies.expect @@ -10,32 +10,32 @@ use const BAR; abstract class Class7 { - abstract public function abstractFun(); + abstract function abstractFun(); - public function emptyFun() + function emptyFun() { } - public function emptyFun2() + function emptyFun2() { } - public function simple() + function simple() { return 1; } - public function simple2() + function simple2() { return 1; } - public function long() + function long() { if ($member instanceof Method) { $s = [1, 2, 3]; @@ -47,7 +47,7 @@ abstract class Class7 } - public function resolving($a = a\FOO, self $b = null, $c = self::FOO) + function resolving($a = a\FOO, self $b = null, $c = self::FOO) { echo FOO; echo \FOO; @@ -67,7 +67,7 @@ abstract class Class7 } - public function complex() + function complex() { echo 1; // single line comment diff --git a/tests/PhpGenerator/expected/Extractor.bodies.resolving.expect b/tests/PhpGenerator/expected/Extractor.bodies.resolving.expect index d2e1236f..efe51b99 100644 --- a/tests/PhpGenerator/expected/Extractor.bodies.resolving.expect +++ b/tests/PhpGenerator/expected/Extractor.bodies.resolving.expect @@ -5,32 +5,32 @@ use Abc\a\func; abstract class Class7 { - abstract public function abstractFun(); + abstract function abstractFun(); - public function emptyFun() + function emptyFun() { } - public function emptyFun2() + function emptyFun2() { } - public function simple() + function simple() { return 1; } - public function simple2() + function simple2() { return 1; } - public function long() + function long() { if ($member instanceof \Abc\Method) { $s = [1, 2, 3]; @@ -42,7 +42,7 @@ abstract class Class7 } - public function resolving($a = \Abc\a\FOO, self $b = null, $c = self::FOO) + function resolving($a = \Abc\a\FOO, self $b = null, $c = self::FOO) { echo FOO; echo \FOO; @@ -62,7 +62,7 @@ abstract class Class7 } - public function complex() + function complex() { echo 1; // single line comment diff --git a/tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect b/tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect index 4aaf061d..8d8a774c 100644 --- a/tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect +++ b/tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect @@ -5,32 +5,32 @@ use Abc\a\func; abstract class Class7 { - abstract public function abstractFun(); + abstract function abstractFun(); - public function emptyFun() + function emptyFun() { } - public function emptyFun2() + function emptyFun2() { } - public function simple() + function simple() { return 1; } - public function simple2() + function simple2() { return 1; } - public function long() + function long() { if ($member instanceof \Abc\Method) { $s = [1, 2, 3]; @@ -42,7 +42,7 @@ abstract class Class7 } - public function resolving($a = \Abc\a\FOO, self $b = null, $c = self::FOO) + function resolving($a = \Abc\a\FOO, self $b = null, $c = self::FOO) { echo FOO; echo \FOO; @@ -62,7 +62,7 @@ abstract class Class7 } - public function complex() + function complex() { echo 1; // single line comment diff --git a/tests/PhpGenerator/expected/Extractor.classes.80.expect b/tests/PhpGenerator/expected/Extractor.classes.80.expect index 7696e495..4cfd379b 100644 --- a/tests/PhpGenerator/expected/Extractor.classes.80.expect +++ b/tests/PhpGenerator/expected/Extractor.classes.80.expect @@ -21,7 +21,7 @@ class Class9 /** Commented */ #[ExampleAttribute] #[WithArguments(true)] - public const FOO = 123; + const FOO = 123; /** @var resource */ #[ExampleAttribute] @@ -42,7 +42,7 @@ class Class10 public string|int $prop; - public function test(mixed $param): string|int + function test(mixed $param): string|int { } } diff --git a/tests/PhpGenerator/expected/Extractor.classes.expect b/tests/PhpGenerator/expected/Extractor.classes.expect index a4485052..a2d09443 100644 --- a/tests/PhpGenerator/expected/Extractor.classes.expect +++ b/tests/PhpGenerator/expected/Extractor.classes.expect @@ -14,7 +14,7 @@ namespace Abc; */ interface Interface1 { - function func1(); + public function func1(); } interface Interface2 @@ -82,7 +82,7 @@ class Class3 class Class4 { - public const THE_CONSTANT = 9; + const THE_CONSTANT = 9; } class Class5 diff --git a/tests/PhpGenerator/expected/Extractor.enum.expect b/tests/PhpGenerator/expected/Extractor.enum.expect index f3c8e7b2..aa2a134d 100644 --- a/tests/PhpGenerator/expected/Extractor.enum.expect +++ b/tests/PhpGenerator/expected/Extractor.enum.expect @@ -10,8 +10,8 @@ namespace Abc; #[\ExampleAttribute] enum Enum1 { - public const FOO = 123; - public const BAR = self::Clubs; + const FOO = 123; + const BAR = self::Clubs; /** Commented */ case Clubs; @@ -31,14 +31,14 @@ enum Enum2: string implements \Countable case GET = 'get'; case POST = 'post'; - public function count(): int + function count(): int { } } enum Enum3: int { - public const FOO = 123; + const FOO = 123; case A = self::FOO; case B = 20 + 5; From 23110ddbcdc7723f5ae06e94a535bbd01b72bfdc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 9 Mar 2022 22:31:38 +0100 Subject: [PATCH 119/266] Extractor: supports promoted parameters [Closes #103] --- src/PhpGenerator/Extractor.php | 5 ++++- tests/PhpGenerator/expected/Extractor.classes.80.expect | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 8fc98d32..beb6dca6 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -390,7 +390,10 @@ private function setupFunction(GlobalFunction|Method $function, Node\FunctionLik $function->setReturnReference($node->returnsByRef()); $function->setReturnType($node->getReturnType() ? $this->toPhp($node->getReturnType()) : null); foreach ($node->getParams() as $item) { - $param = $function->addParameter($item->var->name); + $visibility = $this->toVisibility($item->flags); + $param = $visibility + ? ($function->addPromotedParameter($item->var->name))->setVisibility($visibility) + : $function->addParameter($item->var->name); $param->setType($item->type ? $this->toPhp($item->type) : null); $param->setReference($item->byRef); $function->setVariadic($item->variadic); diff --git a/tests/PhpGenerator/expected/Extractor.classes.80.expect b/tests/PhpGenerator/expected/Extractor.classes.80.expect index 4cfd379b..e78d8681 100644 --- a/tests/PhpGenerator/expected/Extractor.classes.80.expect +++ b/tests/PhpGenerator/expected/Extractor.classes.80.expect @@ -6,8 +6,11 @@ namespace Abc; class Class8 { - public function __construct($a, int|string $b = 10, $c = null) - { + public function __construct( + public $a, + private int|string $b = 10, + $c = null, + ) { } } From 5de75b65c683863b2a8ffaf5ee5441e7dd2f33e8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 14 Mar 2022 17:14:27 +0100 Subject: [PATCH 120/266] TraitType: traits cannot have constants --- src/PhpGenerator/Factory.php | 4 +++- src/PhpGenerator/Printer.php | 10 ++++++++-- src/PhpGenerator/TraitType.php | 3 --- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 34cd8caa..392455b8 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -137,7 +137,9 @@ public function fromClassReflection( } } - $class->setConstants($consts); + if ($consts) { + $class->setConstants($consts); + } if ($cases) { $class->setCases($cases); } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 983dde90..45330ea6 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -161,12 +161,10 @@ public function printClass(ClassLike $class, ?PhpNamespace $namespace = null): s } $consts = []; - $methods = []; if ( $class instanceof ClassType || $class instanceof InterfaceType || $class instanceof EnumType - || $class instanceof TraitType ) { foreach ($class->getConstants() as $const) { $def = ($const->isFinal() ? 'final ' : '') @@ -178,7 +176,15 @@ public function printClass(ClassLike $class, ?PhpNamespace $namespace = null): s . $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()); } diff --git a/src/PhpGenerator/TraitType.php b/src/PhpGenerator/TraitType.php index 57621ad4..4dbf42df 100644 --- a/src/PhpGenerator/TraitType.php +++ b/src/PhpGenerator/TraitType.php @@ -20,7 +20,6 @@ */ final class TraitType extends ClassLike { - use Traits\ConstantsAware; use Traits\MethodsAware; use Traits\PropertiesAware; use Traits\TraitsAware; @@ -29,7 +28,6 @@ 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], @@ -45,7 +43,6 @@ 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); From 5c75670b34b12d7bfbae74ed479abe5d350ce6de Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 2 Apr 2022 18:27:34 +0200 Subject: [PATCH 121/266] Printer: always prints trailing comma [Closes #106] --- src/PhpGenerator/Printer.php | 4 ++-- tests/PhpGenerator/Closure.long.phpt | 4 ++-- tests/PhpGenerator/Method.longParams.phpt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 45330ea6..994919e4 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -63,7 +63,7 @@ public function printClosure(Closure $closure, ?PhpNamespace $namespace = null): } $useStr = strlen($tmp = implode(', ', $uses)) > $this->wrapLength && count($uses) > 1 - ? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . "\n" + ? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . ",\n" : $tmp; $body = Helpers::simplifyTaggedNames($closure->getBody(), $this->namespace); @@ -345,7 +345,7 @@ protected function printParameters(Closure|GlobalFunction|Method $function, int $line = implode(', ', $params); return count($params) > 1 && ($special || strlen($line) + $column > $this->wrapLength) - ? "(\n" . $this->indent(implode(",\n", $params)) . ($special ? ',' : '') . "\n)" + ? "(\n" . $this->indent(implode(",\n", $params)) . ",\n)" : "($line)"; } diff --git a/tests/PhpGenerator/Closure.long.phpt b/tests/PhpGenerator/Closure.long.phpt index a6b0a4db..516f01da 100644 --- a/tests/PhpGenerator/Closure.long.phpt +++ b/tests/PhpGenerator/Closure.long.phpt @@ -33,7 +33,7 @@ same( $abcdq, $abcdr, $abcds, - $abcdt + $abcdt, ) use ( $abcde, $abcdf, @@ -50,7 +50,7 @@ same( $abcdq, $abcdr, $abcds, - $abcdt + $abcdt, ) { return null; }', diff --git a/tests/PhpGenerator/Method.longParams.phpt b/tests/PhpGenerator/Method.longParams.phpt index eb723a2a..dd74db5a 100644 --- a/tests/PhpGenerator/Method.longParams.phpt +++ b/tests/PhpGenerator/Method.longParams.phpt @@ -27,7 +27,7 @@ same( string $i, string $j, string $k, - string $l + string $l, ) { return null; } From cebeb1c7bce9d9119e9690056790bfd1979f9bde Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 22 Mar 2022 19:22:11 +0100 Subject: [PATCH 122/266] cs --- src/PhpGenerator/Dumper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 6f9538e1..be905a50 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -62,7 +62,7 @@ private function dumpVar(mixed &$var, array $parents = [], int $level = 0, int $ private function dumpString(string $s): string { - static $special = [ + $special = [ "\r" => '\r', "\n" => '\n', "\t" => '\t', From 26180715431b679c1a44446b02074c3942d43b8c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 2 Apr 2022 18:37:48 +0200 Subject: [PATCH 123/266] rearranged some tests --- tests/PhpGenerator/ClassType.from.74.phpt | 17 ------ tests/PhpGenerator/ClassType.from.80.phpt | 19 ------- tests/PhpGenerator/ClassType.from.enum.phpt | 18 ------ tests/PhpGenerator/ClassType.from.phpt | 4 ++ ...e.attributes.80.phpt => Closure.from.phpt} | 4 -- tests/PhpGenerator/Closure.phpt | 10 ---- tests/PhpGenerator/Dumper.dump().enum.phpt | 3 +- tests/PhpGenerator/EnumType.from.phpt | 18 ++++++ .../{ClassType.enum.phpt => EnumType.phpt} | 0 tests/PhpGenerator/Extractor.extractAll.phpt | 6 -- .../GlobalFunction.attributes.80.phpt | 32 ----------- tests/PhpGenerator/GlobalFunction.from.phpt | 51 +++++++++++++++++ tests/PhpGenerator/GlobalFunction.phpt | 55 ++++--------------- ...mCode.phpt => InterfaceType.fromCode.phpt} | 0 ...Type.interface.phpt => InterfaceType.phpt} | 6 +- .../expected/ClassType.from.74.expect | 7 --- .../expected/ClassType.from.80.expect | 45 --------------- .../expected/ClassType.from.expect | 54 ++++++++++++++++++ ....from.enum.expect => EnumType.from.expect} | 0 .../expected/Extractor.classes.74.expect | 13 ----- .../expected/Extractor.classes.80.expect | 51 ----------------- .../expected/Extractor.classes.expect | 54 ++++++++++++++++++ ....interface.expect => InterfaceType.expect} | 0 tests/PhpGenerator/fixtures/classes.74.php | 13 ----- tests/PhpGenerator/fixtures/classes.80.php | 50 ----------------- tests/PhpGenerator/fixtures/classes.php | 55 +++++++++++++++++++ 26 files changed, 250 insertions(+), 335 deletions(-) delete mode 100644 tests/PhpGenerator/ClassType.from.74.phpt delete mode 100644 tests/PhpGenerator/ClassType.from.80.phpt delete mode 100644 tests/PhpGenerator/ClassType.from.enum.phpt rename tests/PhpGenerator/{Closure.attributes.80.phpt => Closure.from.phpt} (91%) create mode 100644 tests/PhpGenerator/EnumType.from.phpt rename tests/PhpGenerator/{ClassType.enum.phpt => EnumType.phpt} (100%) delete mode 100644 tests/PhpGenerator/GlobalFunction.attributes.80.phpt create mode 100644 tests/PhpGenerator/GlobalFunction.from.phpt rename tests/PhpGenerator/{ClassType.fromCode.phpt => InterfaceType.fromCode.phpt} (100%) rename tests/PhpGenerator/{ClassType.interface.phpt => InterfaceType.phpt} (73%) delete mode 100644 tests/PhpGenerator/expected/ClassType.from.74.expect delete mode 100644 tests/PhpGenerator/expected/ClassType.from.80.expect rename tests/PhpGenerator/expected/{ClassType.from.enum.expect => EnumType.from.expect} (100%) delete mode 100644 tests/PhpGenerator/expected/Extractor.classes.74.expect delete mode 100644 tests/PhpGenerator/expected/Extractor.classes.80.expect rename tests/PhpGenerator/expected/{ClassType.interface.expect => InterfaceType.expect} (100%) delete mode 100644 tests/PhpGenerator/fixtures/classes.74.php delete mode 100644 tests/PhpGenerator/fixtures/classes.80.php diff --git a/tests/PhpGenerator/ClassType.from.74.phpt b/tests/PhpGenerator/ClassType.from.74.phpt deleted file mode 100644 index a5c9c338..00000000 --- a/tests/PhpGenerator/ClassType.from.74.phpt +++ /dev/null @@ -1,17 +0,0 @@ -fromClassReflection(new ReflectionObject($obj)); $res[] = ClassType::from(Abc\Class4::class); $res[] = ClassType::from(Abc\Class5::class); $res[] = ClassType::from(Abc\Class6::class); +$res[] = ClassType::from(Abc\Class7::class); +$res[] = ClassType::from(Abc\Class8::class); +$res[] = ClassType::from(Abc\Class9::class); +$res[] = ClassType::from(Abc\Class10::class); sameFile(__DIR__ . '/expected/ClassType.from.expect', implode("\n", $res)); diff --git a/tests/PhpGenerator/Closure.attributes.80.phpt b/tests/PhpGenerator/Closure.from.phpt similarity index 91% rename from tests/PhpGenerator/Closure.attributes.80.phpt rename to tests/PhpGenerator/Closure.from.phpt index aeacfcc2..d1577874 100644 --- a/tests/PhpGenerator/Closure.attributes.80.phpt +++ b/tests/PhpGenerator/Closure.from.phpt @@ -1,9 +1,5 @@ e Assert::type(Nette\PhpGenerator\PhpFile::class, $file); sameFile(__DIR__ . '/expected/Extractor.classes.expect', (string) $file); -$file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.74.php')))->extractAll(); -sameFile(__DIR__ . '/expected/Extractor.classes.74.expect', (string) $file); - -$file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.80.php')))->extractAll(); -sameFile(__DIR__ . '/expected/Extractor.classes.80.expect', (string) $file); - $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.81.php')))->extractAll(); sameFile(__DIR__ . '/expected/Extractor.classes.81.expect', (string) $file); diff --git a/tests/PhpGenerator/GlobalFunction.attributes.80.phpt b/tests/PhpGenerator/GlobalFunction.attributes.80.phpt deleted file mode 100644 index 21aeb842..00000000 --- a/tests/PhpGenerator/GlobalFunction.attributes.80.phpt +++ /dev/null @@ -1,32 +0,0 @@ -addAttribute('ExampleAttribute'); $function->addComment('My Function'); same( - '/** - * My Function - */ -#[ExampleAttribute] -function test() -{ - return $a + $b; -} -', + <<<'XX' + /** + * My Function + */ + #[ExampleAttribute] + function test() + { + return $a + $b; + } + + XX, (string) $function, ); - - -/** global */ -function func(stdClass $a, $b = null) -{ - echo sprintf('hello, %s', 'world'); - return 1; -} - - -$function = GlobalFunction::from('func'); -same( - '/** - * global - */ -function func(stdClass $a, $b = null) -{ -} -', - (string) $function, -); - - -$function = GlobalFunction::from('func', withBody: true); -same(<<<'XX' - /** - * global - */ - function func(stdClass $a, $b = null) - { - echo \sprintf('hello, %s', 'world'); - return 1; - } - - XX, (string) $function); diff --git a/tests/PhpGenerator/ClassType.fromCode.phpt b/tests/PhpGenerator/InterfaceType.fromCode.phpt similarity index 100% rename from tests/PhpGenerator/ClassType.fromCode.phpt rename to tests/PhpGenerator/InterfaceType.fromCode.phpt diff --git a/tests/PhpGenerator/ClassType.interface.phpt b/tests/PhpGenerator/InterfaceType.phpt similarity index 73% rename from tests/PhpGenerator/ClassType.interface.phpt rename to tests/PhpGenerator/InterfaceType.phpt index 522473a9..0fccbaa9 100644 --- a/tests/PhpGenerator/ClassType.interface.phpt +++ b/tests/PhpGenerator/InterfaceType.phpt @@ -1,9 +1,5 @@ getExtends()); $interface->addMethod('getForm'); -sameFile(__DIR__ . '/expected/ClassType.interface.expect', (string) $interface); +sameFile(__DIR__ . '/expected/InterfaceType.expect', (string) $interface); diff --git a/tests/PhpGenerator/expected/ClassType.from.74.expect b/tests/PhpGenerator/expected/ClassType.from.74.expect deleted file mode 100644 index 24f85e5e..00000000 --- a/tests/PhpGenerator/expected/ClassType.from.74.expect +++ /dev/null @@ -1,7 +0,0 @@ -class Class7 -{ - public \A $a; - public ?\B $b; - public ?\C $c = null; - public ?int $i = 1; -} diff --git a/tests/PhpGenerator/expected/ClassType.from.80.expect b/tests/PhpGenerator/expected/ClassType.from.80.expect deleted file mode 100644 index 06afaec1..00000000 --- a/tests/PhpGenerator/expected/ClassType.from.80.expect +++ /dev/null @@ -1,45 +0,0 @@ -class Class8 -{ - public function __construct( - public $a, - public string|int $b = 10, - $c = null, - ) { - } -} - -/** - * Description of class. - */ -#[\ExampleAttribute] -#[NamedArguments(foo: 'bar', bar: [1, 2, 3])] -class Class9 -{ - /** Commented */ - #[ExampleAttribute] - #[WithArguments(true)] - public const FOO = 123; - - /** @var resource */ - #[ExampleAttribute] - public $handle; - - - /** - * Returns file handle - */ - #[ExampleAttribute] - public function getHandle(#[WithArguments(123)] $mode) - { - } -} - -class Class10 -{ - public string|int $prop; - - - public function test(mixed $param): string|int - { - } -} diff --git a/tests/PhpGenerator/expected/ClassType.from.expect b/tests/PhpGenerator/expected/ClassType.from.expect index 23735a52..b52d045e 100644 --- a/tests/PhpGenerator/expected/ClassType.from.expect +++ b/tests/PhpGenerator/expected/ClassType.from.expect @@ -98,3 +98,57 @@ class Class6 extends Class4 private const THE_PRIVATE_CONSTANT = 9; public const THE_PUBLIC_CONSTANT = 9; } + +class Class7 +{ + public \A $a; + public ?\B $b; + public ?\C $c = null; + public ?int $i = 1; +} + +class Class8 +{ + public function __construct( + public $a, + public string|int $b = 10, + $c = null, + ) { + } +} + +/** + * Description of class. + */ +#[\ExampleAttribute] +#[NamedArguments(foo: 'bar', bar: [1, 2, 3])] +class Class9 +{ + /** Commented */ + #[ExampleAttribute] + #[WithArguments(true)] + public const FOO = 123; + + /** @var resource */ + #[ExampleAttribute] + public $handle; + + + /** + * Returns file handle + */ + #[ExampleAttribute] + public function getHandle(#[WithArguments(123)] $mode) + { + } +} + +class Class10 +{ + public string|int $prop; + + + public function test(mixed $param): string|int + { + } +} diff --git a/tests/PhpGenerator/expected/ClassType.from.enum.expect b/tests/PhpGenerator/expected/EnumType.from.expect similarity index 100% rename from tests/PhpGenerator/expected/ClassType.from.enum.expect rename to tests/PhpGenerator/expected/EnumType.from.expect diff --git a/tests/PhpGenerator/expected/Extractor.classes.74.expect b/tests/PhpGenerator/expected/Extractor.classes.74.expect deleted file mode 100644 index 2b4c373c..00000000 --- a/tests/PhpGenerator/expected/Extractor.classes.74.expect +++ /dev/null @@ -1,13 +0,0 @@ - Date: Sat, 2 Apr 2022 23:16:17 +0200 Subject: [PATCH 124/266] Dumper, Helpers::formatDocComment(), Printer::printFunction() & etc normalizes whitespace --- src/PhpGenerator/Dumper.php | 1 + src/PhpGenerator/Extractor.php | 2 +- src/PhpGenerator/Helpers.php | 3 ++- src/PhpGenerator/Printer.php | 16 ++++++++-------- tests/PhpGenerator/Dumper.dump().phpt | 1 + tests/PhpGenerator/Helpers.comments.phpt | 2 ++ tests/PhpGenerator/Printer.phpt | 6 +++--- 7 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index be905a50..da748508 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -198,6 +198,7 @@ private function dumpObject(object $var, array $parents, int $level): string private function dumpLiteral(Literal $var, int $level): string { $s = $var->formatWith($this); + $s = Nette\Utils\Strings::normalizeNewlines($s); $s = Nette\Utils\Strings::indent(trim($s), $level, $this->indentation); return ltrim($s, $this->indentation); } diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index beb6dca6..d4656102 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -48,7 +48,7 @@ private function parseCode(string $code): void throw new Nette\InvalidStateException('The input string is not a PHP code.'); } - $this->code = str_replace("\r\n", "\n", $code); + $this->code = Nette\Utils\Strings::normalizeNewlines($code); $lexer = new PhpParser\Lexer\Emulative(['usedAttributes' => ['startFilePos', 'endFilePos', 'comments']]); $parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7, $lexer); $stmts = $parser->parse($this->code); diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 1aaaac15..78b1eb38 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -84,7 +84,8 @@ public static function formatDocComment(string $content): string if ($s === '') { return ''; } elseif (str_contains($content, "\n")) { - return str_replace("\n", "\n * ", "/**\n$s") . "\n */\n"; + $s = str_replace("\n", "\n * ", "/**\n$s") . "\n */"; + return Nette\Utils\Strings::normalize($s) . "\n"; } else { return "/** $s */\n"; } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 994919e4..236c8e6b 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -44,13 +44,14 @@ public function printFunction(GlobalFunction $function, ?PhpNamespace $namespace . $function->getName(); $returnType = $this->printReturnType($function); $body = Helpers::simplifyTaggedNames($function->getBody(), $this->namespace); + $body = ltrim(rtrim(Strings::normalize($body)) . "\n"); return Helpers::formatDocComment($function->getComment() . "\n") . self::printAttributes($function->getAttributes()) . $line . $this->printParameters($function, strlen($line) + strlen($returnType) + 2) // 2 = parentheses . $returnType - . "\n{\n" . $this->indent(ltrim(rtrim($body) . "\n")) . "}\n"; + . "\n{\n" . $this->indent($body) . "}\n"; } @@ -66,6 +67,7 @@ public function printClosure(Closure $closure, ?PhpNamespace $namespace = null): ? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . ",\n" : $tmp; $body = Helpers::simplifyTaggedNames($closure->getBody(), $this->namespace); + $body = ltrim(rtrim(Strings::normalize($body)) . "\n"); return self::printAttributes($closure->getAttributes(), inline: true) . 'function ' @@ -73,7 +75,7 @@ public function printClosure(Closure $closure, ?PhpNamespace $namespace = null): . $this->printParameters($closure) . ($uses ? " use ($useStr)" : '') . $this->printReturnType($closure) - . " {\n" . $this->indent(ltrim(rtrim($body) . "\n")) . '}'; + . " {\n" . $this->indent($body) . '}'; } @@ -93,7 +95,7 @@ public function printArrowFunction(Closure $closure, ?PhpNamespace $namespace = . ($closure->getReturnReference() ? '&' : '') . $this->printParameters($closure) . $this->printReturnType($closure) - . ' => ' . trim($body) . ';'; + . ' => ' . trim(Strings::normalize($body)) . ';'; } @@ -110,7 +112,8 @@ public function printMethod(Method $method, ?PhpNamespace $namespace = null, boo . $method->getName(); $returnType = $this->printReturnType($method); $params = $this->printParameters($method, strlen($line) + strlen($returnType) + strlen($this->indentation) + 2); - $body = Helpers::simplifyTaggedNames((string) $method->getBody(), $this->namespace); + $body = Helpers::simplifyTaggedNames($method->getBody(), $this->namespace); + $body = ltrim(rtrim(Strings::normalize($body)) . "\n"); return Helpers::formatDocComment($method->getComment() . "\n") . self::printAttributes($method->getAttributes()) @@ -119,10 +122,7 @@ public function printMethod(Method $method, ?PhpNamespace $namespace = null, boo . $returnType . ($method->isAbstract() || $isInterface ? ";\n" - : (str_contains($params, "\n") ? ' ' : "\n") - . "{\n" - . $this->indent(ltrim(rtrim($body) . "\n")) - . "}\n"); + : (str_contains($params, "\n") ? ' ' : "\n") . "{\n" . $this->indent($body) . "}\n"); } diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index f0736cba..182da2b9 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -49,6 +49,7 @@ Assert::same('\'He\ll\\\\\o \\\'wor\\\\\\\'ld\\\\\'', $dumper->dump('He\ll\\\o \ // literal Assert::same('[$s]', $dumper->dump([new Literal('$s')])); Assert::same("[strlen('hello')]", $dumper->dump([new Literal('strlen(?)', ['hello'])])); +Assert::same("a\nb", $dumper->dump(new Literal("a\r\nb"))); // arrays diff --git a/tests/PhpGenerator/Helpers.comments.phpt b/tests/PhpGenerator/Helpers.comments.phpt index 94335ae1..6268e9d9 100644 --- a/tests/PhpGenerator/Helpers.comments.phpt +++ b/tests/PhpGenerator/Helpers.comments.phpt @@ -17,6 +17,8 @@ Assert::same('', Helpers::formatDocComment(' ')); Assert::same("/** @var string */\n", Helpers::formatDocComment('@var string')); Assert::same("/**\n * @var string\n */\n", Helpers::formatDocComment("@var string\n")); Assert::same("/**\n * A\n * B\n * C\n */\n", Helpers::formatDocComment("A\nB\nC\n")); +Assert::same("/**\n * @var string\n */\n", Helpers::formatDocComment("@var string \r\n")); +Assert::same("/**\n * A\n *\n * B\n */\n", Helpers::formatDocComment("A\n\nB")); Assert::same('', Helpers::unformatDocComment('')); Assert::same('', Helpers::unformatDocComment("/** */\n\r\t")); diff --git a/tests/PhpGenerator/Printer.phpt b/tests/PhpGenerator/Printer.phpt index e1417ed2..a4103927 100644 --- a/tests/PhpGenerator/Printer.phpt +++ b/tests/PhpGenerator/Printer.phpt @@ -45,7 +45,7 @@ $class->addMethod('first') ->addComment('@return resource') ->setFinal(true) ->setReturnType('stdClass') - ->setBody("func();\nreturn ?;", [['aaaaaaaaaaaa' => 1, 'bbbbbbbbbbb' => 2, 'cccccccccccccc' => 3, 'dddddddddddd' => 4, 'eeeeeeeeeeee' => 5, 'ffffffff' => 6]]) + ->setBody("func(); \r\nreturn ?;", [['aaaaaaaaaaaa' => 1, 'bbbbbbbbbbb' => 2, 'cccccccccccccc' => 3, 'dddddddddddd' => 4, 'eeeeeeeeeeee' => 5, 'ffffffff' => 6]]) ->addParameter('var') ->setType('stdClass'); @@ -67,7 +67,7 @@ sameFile(__DIR__ . '/expected/Printer.class-alt.expect', $printer->printClass($c $function = new Nette\PhpGenerator\GlobalFunction('func'); $function ->setReturnType('stdClass') - ->setBody("func();\nreturn 123;") + ->setBody("func(); \r\nreturn 123;") ->addParameter('var') ->setType('stdClass'); @@ -77,7 +77,7 @@ sameFile(__DIR__ . '/expected/Printer.function.expect', $printer->printFunction( $closure = new Nette\PhpGenerator\Closure; $closure ->setReturnType('stdClass') - ->setBody("func();\nreturn 123;") + ->setBody("func(); \r\nreturn 123;") ->addParameter('var') ->setType('stdClass'); From 5691cd2e29703ea7ed63342b324efe8327a0f422 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 11 Apr 2022 00:55:54 +0200 Subject: [PATCH 125/266] Dumper: supports __serialize() --- src/PhpGenerator/Dumper.php | 13 +++++++++++-- tests/PhpGenerator/Dumper.dump().phpt | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index da748508..96465dd8 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -138,7 +138,12 @@ private function dumpArray(array &$var, array $parents, int $level, int $column) /** @param array $parents */ private function dumpObject(object $var, array $parents, int $level): string { - if ($var instanceof \Serializable) { + $class = $var::class; + if (method_exists($var, '__serialize')) { + $data = $this->dump($var->__serialize()); + return '\\' . self::class . "::createObject(\\$class::class, $data)"; + + } elseif ($var instanceof \Serializable) { // deprecated return 'unserialize(' . $this->dumpString(serialize($var)) . ')'; } elseif ($var instanceof \UnitEnum) { @@ -155,7 +160,6 @@ private function dumpObject(object $var, array $parents, int $level): string throw new Nette\InvalidArgumentException('Cannot dump closure.'); } - $class = $var::class; if ((new \ReflectionObject($var))->isAnonymous()) { throw new Nette\InvalidArgumentException('Cannot dump anonymous class.'); @@ -270,6 +274,11 @@ private function dumpArguments(array &$var, int $column, bool $named): string */ public static function createObject(string $class, array $props): object { + if (method_exists($class, '__serialize')) { + $obj = (new \ReflectionClass($class))->newInstanceWithoutConstructor(); + $obj->__unserialize($props); + return $obj; + } return unserialize('O' . substr(serialize($class), 1, -1) . substr(serialize($props), 1)); } } diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index 182da2b9..f77375e6 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -172,6 +172,26 @@ if (PHP_VERSION_ID < 80100) { } +// __serialize +class TestSer +{ + public function __serialize(): array + { + return ['a', 'b']; + } + + + public function __unserialize(array $data): void + { + } +} + + +$dumper = new Dumper; +Assert::same('\Nette\PhpGenerator\Dumper::createObject(\TestSer::class, [\'a\', \'b\'])', $dumper->dump(new TestSer)); +Assert::equal(new TestSer, eval('return ' . $dumper->dump(new TestSer) . ';')); + + // datetime class TestDateTime extends DateTime From 7fe136bfe8595f9ba6648c05ca4ec6237ed8e96b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 5 Apr 2022 17:53:23 +0200 Subject: [PATCH 126/266] Printer: refactoring --- src/PhpGenerator/Printer.php | 61 ++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 236c8e6b..ec185f2f 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -126,8 +126,10 @@ public function printMethod(Method $method, ?PhpNamespace $namespace = null, boo } - public function printClass(ClassLike $class, ?PhpNamespace $namespace = null): string - { + public function printClass( + ClassType|InterfaceType|TraitType|EnumType $class, + ?PhpNamespace $namespace = null, + ): string { $this->namespace = $this->resolveTypes ? $namespace : null; $class->validate(); $resolver = $this->namespace @@ -161,11 +163,7 @@ public function printClass(ClassLike $class, ?PhpNamespace $namespace = null): s } $consts = []; - if ( - $class instanceof ClassType - || $class instanceof InterfaceType - || $class instanceof EnumType - ) { + if ($class instanceof ClassType || $class instanceof InterfaceType || $class instanceof EnumType) { foreach ($class->getConstants() as $const) { $def = ($const->isFinal() ? 'final ' : '') . ($const->getVisibility() ? $const->getVisibility() . ' ' : '') @@ -221,29 +219,32 @@ public function printClass(ClassLike $class, ?PhpNamespace $namespace = null): s . implode(str_repeat("\n", $this->linesBetweenMethods), $methods), ]); - $type = match (true) { - $class instanceof ClassType => $class->getType(), - $class instanceof InterfaceType => 'interface', - $class instanceof TraitType => 'trait', - $class instanceof EnumType => 'enum', - }; + if ($class instanceof ClassType) { + $line[] = $class->isAbstract() ? 'abstract' : null; + $line[] = $class->isFinal() ? 'final' : null; + } - return Strings::normalize( - Helpers::formatDocComment($class->getComment() . "\n") + $line[] = match (true) { + $class instanceof ClassType => $class->getName() ? $class->getType() . ' ' . $class->getName() : null, + $class instanceof InterfaceType => 'interface ' . $class->getName(), + $class instanceof TraitType => 'trait ' . $class->getName(), + $class instanceof EnumType => 'enum ' . $class->getName() . ($enumType ? $this->returnTypeColon . $enumType : ''), + }; + $line[] = ($class instanceof ClassType || $class instanceof InterfaceType) && $class->getExtends() + ? 'extends ' . implode(', ', array_map($resolver, (array) $class->getExtends())) + : null; + $line[] = ($class instanceof ClassType || $class instanceof EnumType) && $class->getImplements() + ? 'implements ' . implode(', ', array_map($resolver, $class->getImplements())) + : null; + $line[] = $class->getName() ? null : '{'; + + return Helpers::formatDocComment($class->getComment() . "\n") . self::printAttributes($class->getAttributes()) - . ($class instanceof ClassType && $class->isAbstract() ? 'abstract ' : '') - . ($class instanceof ClassType && $class->isFinal() ? 'final ' : '') - . ($class->getName() ? $type . ' ' . $class->getName() . ($enumType ? $this->returnTypeColon . $enumType : '') . ' ' : '') - . (($class instanceof ClassType || $class instanceof InterfaceType) && $class->getExtends() - ? 'extends ' . implode(', ', array_map($resolver, (array) $class->getExtends())) . ' ' - : '') - . (($class instanceof ClassType || $class instanceof EnumType) && $class->getImplements() - ? 'implements ' . implode(', ', array_map($resolver, $class->getImplements())) . ' ' - : '') - . ($class->getName() ? "\n" : '') . "{\n" + . implode(' ', array_filter($line)) + . ($class->getName() ? "\n{\n" : "\n") . ($members ? $this->indent(implode("\n", $members)) : '') - . '}', - ) . ($class->getName() ? "\n" : ''); + . '}' + . ($class->getName() ? "\n" : ''); } @@ -286,13 +287,11 @@ public function printFile(PhpFile $file): string $namespaces[] = $this->printNamespace($namespace); } - return Strings::normalize( - "getComment() ? "\n" . Helpers::formatDocComment($file->getComment() . "\n") : '') . "\n" . ($file->hasStrictTypes() ? "declare(strict_types=1);\n\n" : '') - . implode("\n\n", $namespaces), - ) . "\n"; + . implode("\n\n", $namespaces); } From 674f5e508629b13f8709f82b5af492cb9969d0e7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 5 Apr 2022 19:28:35 +0200 Subject: [PATCH 127/266] readme: updated content --- readme.md | 418 +++++++++++++++++++++++++++++------------------------- 1 file changed, 224 insertions(+), 194 deletions(-) diff --git a/readme.md b/readme.md index 2a75b705..05698af8 100644 --- a/readme.md +++ b/readme.md @@ -1,19 +1,21 @@ -Nette PHP Generator +Nette PHP Generator [![Latest Stable Version](https://poser.pugx.org/nette/php-generator/v/stable)](https://github.com/nette/php-generator/releases) [![Downloads this Month](https://img.shields.io/packagist/dm/nette/php-generator.svg)](https://packagist.org/packages/nette/php-generator) =================== -[![Downloads this Month](https://img.shields.io/packagist/dm/nette/php-generator.svg)](https://packagist.org/packages/nette/php-generator) -[![Tests](https://github.com/nette/php-generator/workflows/Tests/badge.svg?branch=master)](https://github.com/nette/php-generator/actions) -[![Coverage Status](https://coveralls.io/repos/github/nette/php-generator/badge.svg?branch=master&v=1)](https://coveralls.io/github/nette/php-generator?branch=master) -[![Latest Stable Version](https://poser.pugx.org/nette/php-generator/v/stable)](https://github.com/nette/php-generator/releases) -[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/php-generator/blob/master/license.md) +✅ Need to generate PHP code for [classes](#classes), [functions](#global-function), [PHP files](#php-files), etc.?
+✅ Supports all the latest PHP features like [enums](#enums), [attributes](#attributes), etc.
+✅ Allows you to easily modify [existing classes](#generating-according-to-existing-ones)
+✅ [PSR-12 compliant](#printers-and-psr-compliance) output
+✅ Highly mature, stable, and widely used library -Introduction +Installation ------------ -Do you need to generate PHP code of classes, functions, namespaces, etc.? This library with a friendly API will help you. +```shell +composer require nette/php-generator +``` -Documentation can be found on the [website](https://doc.nette.org/php-generator). +For PHP compatibility, see the [table](#compatibility-table). Documentation even for older versions can be found on the [library's website](https://doc.nette.org/php-generator). [Support Me](https://github.com/sponsors/dg) @@ -26,32 +28,15 @@ Do you like PHP Generator? Are you looking forward to the new features? Thank you! -Installation ------------- - -```shell -composer require nette/php-generator -``` - -- PhpGenerator 4.0 is compatible with PHP 8.0 to 8.1 -- PhpGenerator 3.6 is compatible with PHP 7.2 to 8.1 -- PhpGenerator 3.2 – 3.5 is compatible with PHP 7.1 to 8.0 -- PhpGenerator 3.1 is compatible with PHP 7.1 to 7.3 -- PhpGenerator 3.0 is compatible with PHP 7.0 to 7.3 -- PhpGenerator 2.6 is compatible with PHP 5.6 to 7.3 - - - Classes ------- -Let's start with a straightforward example of generating class using [ClassType](https://api.nette.org/php-generator/Nette/PhpGenerator/ClassType.html): +Let's start with a straightforward example of generating class using [ClassType](https://api.nette.org/php-generator/master/Nette/PhpGenerator/ClassType.html): ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class - ->setFinal() +$class->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) ->addComment("Description of class.\nSecond line\n") @@ -75,14 +60,14 @@ final class Demo extends ParentClass implements Countable } ``` -We can also use a printer to generate the code, which, unlike `echo $class`, we will be able to further configure: +We can also use a printer to generate the code, which, unlike `echo $class`, we will be able to [further configure](#printers-and-psr-compliance): ```php $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); ``` -We can add constants ([Constant](https://api.nette.org/php-generator/Nette/PhpGenerator/Constant.html)) and properties ([Property](https://api.nette.org/php-generator/Nette/PhpGenerator/Property.html)): +We can add constants (class [Constant](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Constant.html)) and properties (class [Property](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Property.html)): ```php $class->addConstant('ID', 123) @@ -95,8 +80,7 @@ $class->addProperty('items', [1, 2, 3]) ->addComment('@var int[]'); $class->addProperty('list') - ->setType('array') - ->setNullable() + ->setType('?array') ->setInitialized(); // prints '= null' ``` @@ -111,21 +95,19 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -And we can add methods with parameters: +And we can add [methods](#Method-and-Function-Signature): ```php $method = $class->addMethod('count') ->addComment('Count it.') - ->addComment('@return int') ->setFinal() ->setProtected() - ->setReturnType('int') // method return type - ->setReturnNullable() // nullable return type + ->setReturnType('?int') // method return type ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] ``` It results in: @@ -133,7 +115,6 @@ It results in: ```php /** * Count it. - * @return int */ final protected function count(array &$items = []): ?int { @@ -147,7 +128,7 @@ Promoted parameters introduced by PHP 8.0 can be passed to the constructor: $method = $class->addMethod('__construct'); $method->addPromotedParameter('name'); $method->addPromotedParameter('args', []) - ->setPrivate(); + ->setPrivate(); ``` It results in: @@ -189,54 +170,48 @@ $methodRecount = $methodCount->cloneWithName('recount'); $class->addMember($methodRecount); ``` -Types ------ +Interface or Trait +------------------ -Each type or union/intersection type can be passed as a string, you can also use predefined constants for native types: +You can create interfaces and traits (classes [InterfaceType](https://api.nette.org/php-generator/master/Nette/PhpGenerator/InterfaceType.html) and [TraitType](https://api.nette.org/php-generator/master/Nette/PhpGenerator/TraitType.html)): ```php -use Nette\PhpGenerator\Type; - -$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 +$interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); +$trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -The same applies to the method `setReturnType()`. - - -Tabs versus Spaces ------------------- - -The generated code uses tabs for indentation. If you want to have the output compatible with PSR-2 or PSR-12, use `PsrPrinter`: +Using traits: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -// ... - -$printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 spaces indentation +$class->addTrait('SmartObject'); +$class->addTrait('MyTrait') + ->addResolution('sayHello as protected') + ->addComment('@use MyTrait'); +echo $class; ``` - -Interface or Trait ------------------- - -You can create interfaces and traits: +Result: ```php -$interface = Nette\PhpGenerator\InterfaceType('MyInterface'); -$trait = Nette\PhpGenerator\TraitType('MyTrait'); +class Demo +{ + use SmartObject; + /** @use MyTrait */ + use MyTrait { + sayHello as protected; + } +} ``` + Enums ----- -You can easily create the enums that PHP 8.1 brings: +You can easily create the enums that PHP 8.1 brings (class [EnumType](https://api.nette.org/php-generator/master/Nette/PhpGenerator/EnumType.html)): ```php -$enum = Nette\PhpGenerator\EnumType('Suit'); +$enum = new Nette\PhpGenerator\EnumType('Suit'); $enum->addCase('Clubs'); $enum->addCase('Diamonds'); $enum->addCase('Hearts'); @@ -250,10 +225,10 @@ Result: ```php enum Suit { - case Clubs; - case Diamonds; - case Hearts; - case Spades; + case Clubs; + case Diamonds; + case Hearts; + case Spades; } ``` @@ -267,71 +242,6 @@ $enum->addCase('Diamonds', '♦'); It is possible to add a comment or [attributes](#attributes) to each case using `addComment()` or `addAttribute()`. -Literals --------- - -With `Literal` you can pass arbitrary PHP code to, for example, default property or parameter values etc: - -```php -use Nette\PhpGenerator\Literal; - -$class = new Nette\PhpGenerator\ClassType('Demo'); - -$class->addProperty('foo', new Literal('Iterator::SELF_FIRST')); - -$class->addMethod('bar') - ->addParameter('id', new Literal('1 + 2')); - -echo $class; -``` - -Result: - -```php -class Demo -{ - public $foo = Iterator::SELF_FIRST; - - public function bar($id = 1 + 2) - { - } -} -``` - -You can also pass parameters to `Literal` and have it formatted into valid PHP code using [special placeholders](#method-and-function-body-generator): - -```php -new Literal('substr(?, ?)', [$a, $b]); -// generates, for example: substr('hello', 5); -``` - - - -Using Traits ------------- - -```php -$class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addTrait('SmartObject'); -$class->addTrait('MyTrait') - ->addResolution('sayHello as protected') - ->addComment('@use MyTrait'); -echo $class; -``` - -Result: - -```php -class Demo -{ - use SmartObject; - /** @use MyTrait */ - use MyTrait { - sayHello as protected; - } -} -``` - Anonymous Class --------------- @@ -359,7 +269,7 @@ $obj = new class ($val) { Global Function --------------- -Code of functions will generate class [GlobalFunction](https://api.nette.org/php-generator/Nette/PhpGenerator/GlobalFunction.html): +Code of functions will generate class [GlobalFunction](https://api.nette.org/php-generator/master/Nette/PhpGenerator/GlobalFunction.html): ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -368,7 +278,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// or use PsrPrinter for output compatible with PSR-2 / PSR-12 +// or use PsrPrinter for output conforming to PSR-2 / PSR-12 // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -384,7 +294,7 @@ function foo($a, $b) Closure ------- -Code of closures will generate class [Closure](https://api.nette.org/php-generator/Nette/PhpGenerator/Closure.html): +Code of closures will generate class [Closure](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Closure.html): ```php $closure = new Nette\PhpGenerator\Closure; @@ -395,7 +305,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// or use PsrPrinter for output compatible with PSR-2 / PSR-12 +// or use PsrPrinter for output conforming to PSR-2 / PSR-12 // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -418,7 +328,6 @@ $closure->setBody('$a + $b'); $closure->addParameter('a'); $closure->addParameter('b'); -// or use PsrPrinter for output compatible with PSR-2 / PSR-12 echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure); ``` @@ -428,46 +337,47 @@ Result: fn($a, $b) => $a + $b ``` -Attributes ----------- +Method and Function Signature +----------------------------- -You can add PHP 8 attributes to all classes, methods, properties, constants, enum cases, functions, closures and parameters. +Methods are represented by the class [Method](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Method.html). You can set visibility, return value, add comments, [attributes|#Attributes] etc: ```php -$class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); - -$class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); - $method = $class->addMethod('count') - ->addAttribute('Foo\Cached', ['mode' => true]); + ->addComment('Count it.') + ->setFinal() + ->setProtected() + ->setReturnType('?int'); +``` -$method->addParameter('items') - ->addAttribute('Bar'); +Each parameter is represented by a class [Parameter](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Parameter.html). Again, you can set every conceivable property: -echo $class; +```php +$method->addParameter('items', []) // $items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] + +// function count(&$items = []) ``` -Result: +To define the so-called variadics parameters (or also the splat, spread, ellipsis, unpacking or three dots operator), use `setVariadics()`: ```php -#[Deprecated] -class Demo -{ - #[WithArguments(1, 2)] - public $list; +$method = $class->addMethod('count'); +$method->setVariadics(true); +$method->addParameter('items'); +``` +Generates: - #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { - } +```php +function count(...$items) +{ } ``` -Method and Function Body Generator ----------------------------------- +Method and Function Body +------------------------ The body can be passed to the `setBody()` method at once or sequentially (line by line) by repeatedly calling `addBody()`: @@ -555,10 +465,132 @@ function foo($a) } ``` +Printers and PSR compliance +--------------------------- + +PHP code is generated by `Printer` objects. There is a `PsrPrinter` whose output conforms to PSR-2 and PSR-12 and uses spaces for indentation, and a `Printer` that uses tabs for indentation. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +// ... + +$printer = new Nette\PhpGenerator\PsrPrinter; +echo $printer->printClass($class); // 4 spaces indentation +``` + +Need to customize printer behavior? Create your own by inheriting the `Printer` class. You can reconfigure these variables: + +```php +class MyPrinter extends Nette\PhpGenerator\Printer +{ + public int $wrapLength = 120; + public string $indentation = "\t"; + public int $linesBetweenProperties = 0; + public int $linesBetweenMethods = 2; + public string $returnTypeColon = ': '; +} +``` + + +Types +----- + +Each type or union/intersection type can be passed as a string, you can also use predefined constants for native types: + +```php +use Nette\PhpGenerator\Type; + +$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 +``` + +The same applies to the method `setReturnType()`. + + +Literals +-------- + +With `Literal` you can pass arbitrary PHP code to, for example, default property or parameter values etc: + +```php +use Nette\PhpGenerator\Literal; + +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('foo', new Literal('Iterator::SELF_FIRST')); + +$class->addMethod('bar') + ->addParameter('id', new Literal('1 + 2')); + +echo $class; +``` + +Result: + +```php +class Demo +{ + public $foo = Iterator::SELF_FIRST; + + public function bar($id = 1 + 2) + { + } +} +``` + +You can also pass parameters to `Literal` and have it formatted into valid PHP code using [special placeholders](#method-and-function-body-generator): + +```php +new Literal('substr(?, ?)', [$a, $b]); +// generates, for example: substr('hello', 5); +``` + + + +Attributes +---------- + +You can add PHP 8 attributes to all classes, methods, properties, constants, enum cases, functions, closures and parameters (class [Attribute](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Attribute.html)). + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$class->addAttribute('Deprecated'); + +$class->addProperty('list') + ->addAttribute('WithArguments', [1, 2]); + +$method = $class->addMethod('count') + ->addAttribute('Foo\Cached', ['mode' => true]); + +$method->addParameter('items') + ->addAttribute('Bar'); + +echo $class; +``` + +Result: + +```php +#[Deprecated] +class Demo +{ + #[WithArguments(1, 2)] + public $list; + + + #[Foo\Cached(mode: true)] + public function count(#[Bar] $items) + { + } +} +``` + Namespace --------- -Classes, traits, interfaces and enums (hereinafter classes) can be grouped into namespaces ([PhpNamespace](https://api.nette.org/php-generator/Nette/PhpGenerator/PhpNamespace.html)): +Classes, traits, interfaces and enums (hereinafter classes) can be grouped into namespaces (class [PhpNamespace](https://api.nette.org/php-generator/master/Nette/PhpGenerator/PhpNamespace.html)): ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); @@ -623,7 +655,7 @@ $method->addParameter('arg') echo $namespace; -// or use PsrPrinter for output compatible with PSR-2 / PSR-12 +// or use PsrPrinter for output conforming to PSR-2 / PSR-12 // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -658,7 +690,7 @@ echo $printer->printNamespace($namespace); PHP Files --------- -Classes, functions and namespaces can be grouped into PHP files represented by the class [PhpFile](https://api.nette.org/php-generator/Nette/PhpGenerator/PhpFile.html): +Classes, functions and namespaces can be grouped into PHP files represented by the class [PhpFile](https://api.nette.org/php-generator/master/Nette/PhpGenerator/PhpFile.html): ```php $file = new Nette\PhpGenerator\PhpFile; @@ -675,7 +707,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// or use PsrPrinter for output compatible with PSR-2 / PSR-12 +// or use PsrPrinter for output conforming to PSR-2 / PSR-12 // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -701,16 +733,19 @@ function foo() } ``` -Generate using Reflection -------------------------- +Generating According to Existing Ones +------------------------------------- -Another common use case is to create class or function based on existing one: +In addition to being able to model classes and functions using the API described above, you can also have them automatically generated using existing ones: ```php +// creates a class identical to the PDO class $class = Nette\PhpGenerator\ClassType::from(PDO::class); +// creates a function identical to trim() $function = Nette\PhpGenerator\GlobalFunction::from('trim'); +// creates a closure as specified $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {} ); @@ -720,15 +755,15 @@ Function and method bodies are empty by default. If you want to load them as wel (it requires `nikic/php-parser` to be installed): ```php -$class = Nette\PhpGenerator\ClassType::from(PDO::class, withBodies: true); +$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); -$function = Nette\PhpGenerator\GlobalFunction::from('dump', withBody: true); +$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -Load class from file --------------------- +Loading from PHP File +--------------------- -You can also load classes directly from a PHP file that is not already loaded or string of PHP code: +You can also load classes and functions directly from a PHP file that is not already loaded or string of PHP code: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<dump($var); // prints ['a', 'b', 123] ``` -Custom Printer --------------- -Need to customize printer behavior? Create your own by inheriting the `Printer` class. You can reconfigure these variables: +Compatibility Table +------------------- -```php -class MyPrinter extends Nette\PhpGenerator\Printer -{ - public int $wrapLength = 120; - public string $indentation = "\t"; - public int $linesBetweenProperties = 0; - public int $linesBetweenMethods = 2; - public string $returnTypeColon = ': '; -} -``` +- PhpGenerator 4.0 is compatible with PHP 8.0 to 8.1 +- PhpGenerator 3.6 is compatible with PHP 7.2 to 8.1 +- PhpGenerator 3.2 – 3.5 is compatible with PHP 7.1 to 8.0 +- PhpGenerator 3.1 is compatible with PHP 7.1 to 7.3 +- PhpGenerator 3.0 is compatible with PHP 7.0 to 7.3 +- PhpGenerator 2.6 is compatible with PHP 5.6 to 7.3 From a88a6c560b9d7e6f2f234444dd7f0e8feacfe300 Mon Sep 17 00:00:00 2001 From: Gabriel Ortega Date: Mon, 23 May 2022 21:05:58 +0200 Subject: [PATCH 128/266] Extractor: added support for Readonly promoted parameters (#111) --- src/PhpGenerator/Extractor.php | 3 ++- .../PhpGenerator/expected/Extractor.classes.81.expect | 11 +++++++++++ tests/PhpGenerator/fixtures/classes.81.php | 11 +++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index d4656102..792656b8 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -391,8 +391,9 @@ private function setupFunction(GlobalFunction|Method $function, Node\FunctionLik $function->setReturnType($node->getReturnType() ? $this->toPhp($node->getReturnType()) : null); foreach ($node->getParams() as $item) { $visibility = $this->toVisibility($item->flags); + $isReadonly = (bool) ($item->flags & Node\Stmt\Class_::MODIFIER_READONLY); $param = $visibility - ? ($function->addPromotedParameter($item->var->name))->setVisibility($visibility) + ? ($function->addPromotedParameter($item->var->name))->setVisibility($visibility)->setReadonly($isReadonly) : $function->addParameter($item->var->name); $param->setType($item->type ? $this->toPhp($item->type) : null); $param->setReference($item->byRef); diff --git a/tests/PhpGenerator/expected/Extractor.classes.81.expect b/tests/PhpGenerator/expected/Extractor.classes.81.expect index 427d927b..831e8e31 100644 --- a/tests/PhpGenerator/expected/Extractor.classes.81.expect +++ b/tests/PhpGenerator/expected/Extractor.classes.81.expect @@ -27,3 +27,14 @@ class Class11 class Attr { } + +class Class12 +{ + private readonly string $bar; + + + public function __construct(private readonly string $foo) + { + $this->bar = "foobar"; + } +} diff --git a/tests/PhpGenerator/fixtures/classes.81.php b/tests/PhpGenerator/fixtures/classes.81.php index 3eeb9208..f283a3f7 100644 --- a/tests/PhpGenerator/fixtures/classes.81.php +++ b/tests/PhpGenerator/fixtures/classes.81.php @@ -27,3 +27,14 @@ public function bar($c = new \stdClass) class Attr { } + +class Class12 +{ + private readonly string $bar; + + + public function __construct(private readonly string $foo) + { + $this->bar = "foobar"; + } +} From 4fec8f78624798183325d804485f7b6a3eb6191d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 2 Jun 2022 13:41:41 +0200 Subject: [PATCH 129/266] fixed GitHub actions --- .github/workflows/coding-style.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index 849bef94..973c7a12 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -27,5 +27,5 @@ jobs: php-version: 8.0 coverage: none - - run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress --ignore-platform-reqs + - run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress - run: php temp/coding-standard/ecs check From f19b7975c7c4d729be5b64fce7eb72f0d4aac6fc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 17 Jun 2022 14:19:45 +0200 Subject: [PATCH 130/266] Printer: added $bracesOnNextLine [Closes #112] --- readme.md | 1 + src/PhpGenerator/Printer.php | 7 +++++-- tests/PhpGenerator/Printer.phpt | 8 ++++---- tests/PhpGenerator/expected/Printer.class-alt.expect | 6 ++---- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/readme.md b/readme.md index 05698af8..2724b7db 100644 --- a/readme.md +++ b/readme.md @@ -487,6 +487,7 @@ class MyPrinter extends Nette\PhpGenerator\Printer public string $indentation = "\t"; public int $linesBetweenProperties = 0; public int $linesBetweenMethods = 2; + public bool $bracesOnNextLine = true; public string $returnTypeColon = ': '; } ``` diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index ec185f2f..2021cee3 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -25,6 +25,7 @@ class Printer public int $linesBetweenProperties = 0; public int $linesBetweenMethods = 2; public string $returnTypeColon = ': '; + public bool $bracesOnNextLine = true; protected ?PhpNamespace $namespace = null; protected ?Dumper $dumper; private bool $resolveTypes = true; @@ -51,7 +52,8 @@ public function printFunction(GlobalFunction $function, ?PhpNamespace $namespace . $line . $this->printParameters($function, strlen($line) + strlen($returnType) + 2) // 2 = parentheses . $returnType - . "\n{\n" . $this->indent($body) . "}\n"; + . ($this->bracesOnNextLine ? "\n" : ' ') + . "{\n" . $this->indent($body) . "}\n"; } @@ -114,6 +116,7 @@ public function printMethod(Method $method, ?PhpNamespace $namespace = null, boo $params = $this->printParameters($method, strlen($line) + strlen($returnType) + strlen($this->indentation) + 2); $body = Helpers::simplifyTaggedNames($method->getBody(), $this->namespace); $body = ltrim(rtrim(Strings::normalize($body)) . "\n"); + $braceOnNextLine = $this->bracesOnNextLine && !str_contains($params, "\n"); return Helpers::formatDocComment($method->getComment() . "\n") . self::printAttributes($method->getAttributes()) @@ -122,7 +125,7 @@ public function printMethod(Method $method, ?PhpNamespace $namespace = null, boo . $returnType . ($method->isAbstract() || $isInterface ? ";\n" - : (str_contains($params, "\n") ? ' ' : "\n") . "{\n" . $this->indent($body) . "}\n"); + : ($braceOnNextLine ? "\n" : ' ') . "{\n" . $this->indent($body) . "}\n"); } diff --git a/tests/PhpGenerator/Printer.phpt b/tests/PhpGenerator/Printer.phpt index a4103927..5a8cd996 100644 --- a/tests/PhpGenerator/Printer.phpt +++ b/tests/PhpGenerator/Printer.phpt @@ -56,14 +56,14 @@ sameFile(__DIR__ . '/expected/Printer.class.expect', $printer->printClass($class sameFile(__DIR__ . '/expected/Printer.method.expect', $printer->printMethod($class->getMethod('first'))); -Assert::with($printer, function () { - $this->linesBetweenProperties = 1; - $this->linesBetweenMethods = 3; -}); +$printer->linesBetweenProperties = 1; +$printer->linesBetweenMethods = 3; +$printer->bracesOnNextLine = false; sameFile(__DIR__ . '/expected/Printer.class-alt.expect', $printer->printClass($class)); +$printer = new Printer; $function = new Nette\PhpGenerator\GlobalFunction('func'); $function ->setReturnType('stdClass') diff --git a/tests/PhpGenerator/expected/Printer.class-alt.expect b/tests/PhpGenerator/expected/Printer.class-alt.expect index 17defce8..ee340d14 100644 --- a/tests/PhpGenerator/expected/Printer.class-alt.expect +++ b/tests/PhpGenerator/expected/Printer.class-alt.expect @@ -44,8 +44,7 @@ final class Example extends ParentClass implements IExample /** * @return resource */ - final public function first(stdClass $var): stdClass - { + final public function first(stdClass $var): stdClass { func(); return [ 'aaaaaaaaaaaa' => 1, @@ -59,7 +58,6 @@ final class Example extends ParentClass implements IExample - public function second() - { + public function second() { } } From 90c76b5e4b4a89befa4044b3c9e5eb8b6beaa266 Mon Sep 17 00:00:00 2001 From: Jeroen <1517978+Jeroeny@users.noreply.github.com> Date: Tue, 23 Aug 2022 22:19:16 +0200 Subject: [PATCH 131/266] Printer: added $linesBetweenUseTypes (#114) --- readme.md | 1 + src/PhpGenerator/Printer.php | 11 +++++++---- src/PhpGenerator/PsrPrinter.php | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 2724b7db..e50a30c3 100644 --- a/readme.md +++ b/readme.md @@ -487,6 +487,7 @@ class MyPrinter extends Nette\PhpGenerator\Printer public string $indentation = "\t"; public int $linesBetweenProperties = 0; public int $linesBetweenMethods = 2; + public int $linesBetweenUseTypes = 0; public bool $bracesOnNextLine = true; public string $returnTypeColon = ': '; } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 2021cee3..3a015a98 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -24,6 +24,7 @@ class Printer public string $indentation = "\t"; public int $linesBetweenProperties = 0; public int $linesBetweenMethods = 2; + public int $linesBetweenUseTypes = 0; public string $returnTypeColon = ': '; public bool $bracesOnNextLine = true; protected ?PhpNamespace $namespace = null; @@ -255,9 +256,12 @@ public function printNamespace(PhpNamespace $namespace): string { $this->namespace = $this->resolveTypes ? $namespace : null; $name = $namespace->getName(); - $uses = $this->printUses($namespace) - . $this->printUses($namespace, PhpNamespace::NameFunction) - . $this->printUses($namespace, PhpNamespace::NameConstant); + $uses = [ + $this->printUses($namespace), + $this->printUses($namespace, PhpNamespace::NameFunction), + $this->printUses($namespace, PhpNamespace::NameConstant), + ]; + $uses = implode(str_repeat("\n", $this->linesBetweenUseTypes), array_filter($uses)); $items = []; foreach ($namespace->getClasses() as $class) { @@ -305,7 +309,6 @@ protected function printUses(PhpNamespace $namespace, string $of = PhpNamespace: PhpNamespace::NameFunction => 'function ', PhpNamespace::NameConstant => 'const ', ][$of]; - $name = $namespace->getName(); $uses = []; foreach ($namespace->getUses($of) as $alias => $original) { $uses[] = Helpers::extractShortName($original) === $alias diff --git a/src/PhpGenerator/PsrPrinter.php b/src/PhpGenerator/PsrPrinter.php index 7ce72a26..ab62c174 100644 --- a/src/PhpGenerator/PsrPrinter.php +++ b/src/PhpGenerator/PsrPrinter.php @@ -17,4 +17,5 @@ final class PsrPrinter extends Printer { public string $indentation = ' '; public int $linesBetweenMethods = 1; + public int $linesBetweenUseTypes = 1; } From ef5978d47b304864f250753ffb8a9bb186d10c4d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 6 Sep 2022 03:36:36 +0200 Subject: [PATCH 132/266] Dumper::dumpObject() refactoring --- src/PhpGenerator/Dumper.php | 35 +++++++++++++-------------- tests/PhpGenerator/Dumper.dump().phpt | 10 ++++---- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 96465dd8..1ab2a608 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -139,9 +139,9 @@ private function dumpArray(array &$var, array $parents, int $level, int $column) private function dumpObject(object $var, array $parents, int $level): string { $class = $var::class; - if (method_exists($var, '__serialize')) { - $data = $this->dump($var->__serialize()); - return '\\' . self::class . "::createObject(\\$class::class, $data)"; + + if (in_array($class, [\DateTime::class, \DateTimeImmutable::class], true)) { + return $this->format("new \\$class(?, new \\DateTimeZone(?))", $var->format('Y-m-d H:i:s.u'), $var->getTimeZone()->getName()); } elseif ($var instanceof \Serializable) { // deprecated return 'unserialize(' . $this->dumpString(serialize($var)) . ')'; @@ -158,29 +158,28 @@ private function dumpObject(object $var, array $parents, int $level): string } throw new Nette\InvalidArgumentException('Cannot dump closure.'); - } - if ((new \ReflectionObject($var))->isAnonymous()) { + } elseif ((new \ReflectionObject($var))->isAnonymous()) { throw new Nette\InvalidArgumentException('Cannot dump anonymous class.'); - } elseif (in_array($class, [\DateTime::class, \DateTimeImmutable::class], true)) { - return $this->format("new \\$class(?, new \\DateTimeZone(?))", $var->format('Y-m-d H:i:s.u'), $var->getTimeZone()->getName()); + } elseif ($level > $this->maxDepth || in_array($var, $parents, true)) { + throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.'); } - $arr = (array) $var; - $space = str_repeat($this->indentation, $level); - - if ($level > $this->maxDepth || in_array($var, $parents, true)) { - throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.'); + if (method_exists($var, '__serialize')) { + $arr = $var->__serialize(); + } else { + $arr = (array) $var; + if (method_exists($var, '__sleep')) { + foreach ($var->__sleep() as $v) { + $props[$v] = $props["\x00*\x00$v"] = $props["\x00$class\x00$v"] = true; + } + } } + $space = str_repeat($this->indentation, $level); $out = "\n"; $parents[] = $var; - if (method_exists($var, '__sleep')) { - foreach ($var->__sleep() as $v) { - $props[$v] = $props["\x00*\x00$v"] = $props["\x00$class\x00$v"] = true; - } - } foreach ($arr as $k => &$v) { if (!isset($props) || isset($props[$k])) { @@ -195,7 +194,7 @@ private function dumpObject(object $var, array $parents, int $level): string $out .= $space; return $class === \stdClass::class ? "(object) [$out]" - : '\\' . self::class . "::createObject('$class', [$out])"; + : '\\' . self::class . "::createObject(\\$class::class, [$out])"; } diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index f77375e6..bb59e060 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -86,7 +86,7 @@ class Test } Assert::same( - "\\Nette\\PhpGenerator\\Dumper::createObject('Test', [\n\t'a' => 1,\n\t\"\\x00*\\x00b\" => 2,\n\t\"\\x00Test\\x00c\" => 3,\n])", + "\\Nette\\PhpGenerator\\Dumper::createObject(\\Test::class, [\n\t'a' => 1,\n\t\"\\x00*\\x00b\" => 2,\n\t\"\\x00Test\\x00c\" => 3,\n])", $dumper->dump(new Test), ); Assert::equal(new Test, eval('return ' . $dumper->dump(new Test) . ';')); @@ -112,8 +112,8 @@ class Test2 extends Test Assert::same( PHP_VERSION_ID < 80100 - ? "\\Nette\\PhpGenerator\\Dumper::createObject('Test2', [\n\t\"\\x00Test2\\x00c\" => 4,\n\t'a' => 1,\n\t\"\\x00*\\x00b\" => 2,\n])" - : "\\Nette\\PhpGenerator\\Dumper::createObject('Test2', [\n\t'a' => 1,\n\t\"\\x00*\\x00b\" => 2,\n\t\"\\x00Test2\\x00c\" => 4,\n])", + ? "\\Nette\\PhpGenerator\\Dumper::createObject(\\Test2::class, [\n\t\"\\x00Test2\\x00c\" => 4,\n\t'a' => 1,\n\t\"\\x00*\\x00b\" => 2,\n])" + : "\\Nette\\PhpGenerator\\Dumper::createObject(\\Test2::class, [\n\t'a' => 1,\n\t\"\\x00*\\x00b\" => 2,\n\t\"\\x00Test2\\x00c\" => 4,\n])", $dumper->dump(new Test2), ); Assert::equal(new Test2, eval('return ' . $dumper->dump(new Test2) . ';')); @@ -188,7 +188,7 @@ class TestSer $dumper = new Dumper; -Assert::same('\Nette\PhpGenerator\Dumper::createObject(\TestSer::class, [\'a\', \'b\'])', $dumper->dump(new TestSer)); +Assert::same("\\Nette\\PhpGenerator\\Dumper::createObject(\\TestSer::class, [\n\t0 => 'a',\n\t1 => 'b',\n])", $dumper->dump(new TestSer)); Assert::equal(new TestSer, eval('return ' . $dumper->dump(new TestSer) . ';')); @@ -207,7 +207,7 @@ Assert::same( $dumper->dump(new DateTimeImmutable('2016-06-22 20:52:43.1234', new DateTimeZone('Europe/Prague'))), ); same( - "\\Nette\\PhpGenerator\\Dumper::createObject('TestDateTime', [ + "\\Nette\\PhpGenerator\\Dumper::createObject(\\TestDateTime::class, [ 'date' => '2016-06-22 20:52:43.123400', 'timezone_type' => 3, 'timezone' => 'Europe/Prague', From ce449138b73ee788e818afeb41af51df3ec03710 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 6 Sep 2022 03:36:49 +0200 Subject: [PATCH 133/266] support for PHP 8.2 --- .github/workflows/tests.yml | 2 +- composer.json | 2 +- readme.md | 4 ++-- tests/PhpGenerator/ClassType.from.phpt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 82b6f186..54206dba 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['8.0', '8.1'] + php: ['8.0', '8.1', '8.2'] fail-fast: false diff --git a/composer.json b/composer.json index e7e93f59..431cbc9b 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=8.0 <8.2", + "php": ">=8.0 <8.3", "nette/utils": "^3.2.7 || ^4.0" }, "require-dev": { diff --git a/readme.md b/readme.md index e50a30c3..8cd138d3 100644 --- a/readme.md +++ b/readme.md @@ -804,8 +804,8 @@ echo $dumper->dump($var); // prints ['a', 'b', 123] Compatibility Table ------------------- -- PhpGenerator 4.0 is compatible with PHP 8.0 to 8.1 -- PhpGenerator 3.6 is compatible with PHP 7.2 to 8.1 +- PhpGenerator 4.0 is compatible with PHP 8.0 to 8.2 +- PhpGenerator 3.6 is compatible with PHP 7.2 to 8.2 - PhpGenerator 3.2 – 3.5 is compatible with PHP 7.1 to 8.0 - PhpGenerator 3.1 is compatible with PHP 7.1 to 7.3 - PhpGenerator 3.0 is compatible with PHP 7.0 to 7.3 diff --git a/tests/PhpGenerator/ClassType.from.phpt b/tests/PhpGenerator/ClassType.from.phpt index 971a5d0f..837fb11a 100644 --- a/tests/PhpGenerator/ClassType.from.phpt +++ b/tests/PhpGenerator/ClassType.from.phpt @@ -20,7 +20,7 @@ $res[] = ClassType::from(Abc\Interface4::class); $res[] = ClassType::from(Abc\Class1::class); $res[] = ClassType::from(new Abc\Class2); $obj = new Abc\Class3; -$obj->prop2 = 1; +@$obj->prop2 = 1; // dynamic property $res[] = (new Factory)->fromClassReflection(new ReflectionObject($obj)); $res[] = ClassType::from(Abc\Class4::class); $res[] = ClassType::from(Abc\Class5::class); From 445e06364fdfac6c147b175b9f213eda7d126edb Mon Sep 17 00:00:00 2001 From: sisklu <38254887+sisklu@users.noreply.github.com> Date: Mon, 19 Sep 2022 23:20:59 +0200 Subject: [PATCH 134/266] Extractor: Fixed extracting enum method body [Closes #115] (#116) --- src/PhpGenerator/Extractor.php | 3 +-- .../PhpGenerator/Extractor.getMethodBodies.phpt | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 792656b8..d5569f4c 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -66,8 +66,7 @@ public function extractMethodBodies(string $className): array $nodeFinder = new NodeFinder; $classNode = $nodeFinder->findFirst( $this->statements, - fn(Node $node) => ($node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Trait_) - && $node->namespacedName->toString() === $className, + fn(Node $node) => $node instanceof Node\Stmt\ClassLike && $node->namespacedName->toString() === $className, ); $res = []; diff --git a/tests/PhpGenerator/Extractor.getMethodBodies.phpt b/tests/PhpGenerator/Extractor.getMethodBodies.phpt index 1ebf3d6c..4cb6c14a 100644 --- a/tests/PhpGenerator/Extractor.getMethodBodies.phpt +++ b/tests/PhpGenerator/Extractor.getMethodBodies.phpt @@ -35,6 +35,17 @@ abstract class Another echo 123; } } + +enum Color +{ + case Red; + case Blue; + + public function getName(): string + { + return $this->name; + } +} '); $bodies = $extractor->extractMethodBodies('NS\Undefined'); @@ -45,3 +56,8 @@ Assert::same([ 'bar1' => "\$a = 10;\necho 123;", 'bar2' => 'echo "hello";', ], $bodies); + +$bodies = $extractor->extractMethodBodies('NS\Color'); +Assert::same([ + 'getName' => 'return $this->name;', +], $bodies); From 44a7a51a6b080060c23ce6bf4fa618dc251d0eac Mon Sep 17 00:00:00 2001 From: Martin Kluska Date: Wed, 21 Sep 2022 18:52:28 +0200 Subject: [PATCH 135/266] Factory: object class contains path from root namespace (#117) --- src/PhpGenerator/Factory.php | 2 +- tests/PhpGenerator/expected/ClassType.from.81.expect | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 392455b8..979ca1c7 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -272,7 +272,7 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property public function fromObject(object $obj): Literal { - return new Literal('new ' . $obj::class . '(/* unknown */)'); + return new Literal('new \\' . $obj::class . '(/* unknown */)'); } diff --git a/tests/PhpGenerator/expected/ClassType.from.81.expect b/tests/PhpGenerator/expected/ClassType.from.81.expect index 2200d27e..f52acd06 100644 --- a/tests/PhpGenerator/expected/ClassType.from.81.expect +++ b/tests/PhpGenerator/expected/ClassType.from.81.expect @@ -1,4 +1,4 @@ -#[Attr(new Abc\Attr(/* unknown */))] +#[Attr(new \Abc\Attr(/* unknown */))] class Class11 { final public const FOO = 10; @@ -12,7 +12,7 @@ class Class11 } - public function bar($c = new stdClass(/* unknown */)) + public function bar($c = new \stdClass(/* unknown */)) { } } From 22c9ed764452649682ec02199c4212ec8161459e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 4 Oct 2022 01:42:22 +0200 Subject: [PATCH 136/266] Helpers::formatDocComment() added option $forceMultiLine --- src/PhpGenerator/Helpers.php | 4 ++-- tests/PhpGenerator/Helpers.comments.phpt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 78b1eb38..71e7de6f 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -77,13 +77,13 @@ public static function formatArgs(string $statement, array $args): string } - public static function formatDocComment(string $content): string + public static function formatDocComment(string $content, bool $forceMultiLine = false): string { $s = trim($content); $s = str_replace('*/', '* /', $s); if ($s === '') { return ''; - } elseif (str_contains($content, "\n")) { + } elseif ($forceMultiLine || str_contains($content, "\n")) { $s = str_replace("\n", "\n * ", "/**\n$s") . "\n */"; return Nette\Utils\Strings::normalize($s) . "\n"; } else { diff --git a/tests/PhpGenerator/Helpers.comments.phpt b/tests/PhpGenerator/Helpers.comments.phpt index 6268e9d9..e46eab20 100644 --- a/tests/PhpGenerator/Helpers.comments.phpt +++ b/tests/PhpGenerator/Helpers.comments.phpt @@ -16,6 +16,7 @@ require __DIR__ . '/../bootstrap.php'; Assert::same('', Helpers::formatDocComment(' ')); Assert::same("/** @var string */\n", Helpers::formatDocComment('@var string')); Assert::same("/**\n * @var string\n */\n", Helpers::formatDocComment("@var string\n")); +Assert::same("/**\n * @var string\n */\n", Helpers::formatDocComment('@var string', true)); Assert::same("/**\n * A\n * B\n * C\n */\n", Helpers::formatDocComment("A\nB\nC\n")); Assert::same("/**\n * @var string\n */\n", Helpers::formatDocComment("@var string \r\n")); Assert::same("/**\n * A\n *\n * B\n */\n", Helpers::formatDocComment("A\n\nB")); From a73c124f82bec70c800deefd1cea72b9c98ba18e Mon Sep 17 00:00:00 2001 From: Jeroen <1517978+Jeroeny@users.noreply.github.com> Date: Tue, 4 Oct 2022 01:00:56 +0200 Subject: [PATCH 137/266] Printer: allow customizing comment formatting through protected printDocComment (#118) --- src/PhpGenerator/Printer.php | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 3a015a98..48128454 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -48,7 +48,7 @@ public function printFunction(GlobalFunction $function, ?PhpNamespace $namespace $body = Helpers::simplifyTaggedNames($function->getBody(), $this->namespace); $body = ltrim(rtrim(Strings::normalize($body)) . "\n"); - return Helpers::formatDocComment($function->getComment() . "\n") + return $this->printDocComment($function) . self::printAttributes($function->getAttributes()) . $line . $this->printParameters($function, strlen($line) + strlen($returnType) + 2) // 2 = parentheses @@ -119,7 +119,7 @@ public function printMethod(Method $method, ?PhpNamespace $namespace = null, boo $body = ltrim(rtrim(Strings::normalize($body)) . "\n"); $braceOnNextLine = $this->bracesOnNextLine && !str_contains($params, "\n"); - return Helpers::formatDocComment($method->getComment() . "\n") + return $this->printDocComment($method) . self::printAttributes($method->getAttributes()) . $line . $params @@ -144,7 +144,7 @@ public function printClass( if ($class instanceof ClassType || $class instanceof TraitType || $class instanceof EnumType) { foreach ($class->getTraits() as $trait) { $resolutions = $trait->getResolutions(); - $traits[] = Helpers::formatDocComment((string) $trait->getComment()) + $traits[] = $this->printDocComment($trait) . 'use ' . $resolver($trait->getName()) . ($resolutions ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n" @@ -158,7 +158,7 @@ public function printClass( $enumType = $class->getType(); foreach ($class->getCases() as $case) { $enumType ??= is_scalar($case->getValue()) ? get_debug_type($case->getValue()) : null; - $cases[] = Helpers::formatDocComment((string) $case->getComment()) + $cases[] = $this->printDocComment($case) . self::printAttributes($case->getAttributes()) . 'case ' . $case->getName() . ($case->getValue() === null ? '' : ' = ' . $this->dump($case->getValue())) @@ -173,7 +173,7 @@ public function printClass( . ($const->getVisibility() ? $const->getVisibility() . ' ' : '') . 'const ' . $const->getName() . ' = '; - $consts[] = Helpers::formatDocComment((string) $const->getComment()) + $consts[] = $this->printDocComment($const) . self::printAttributes($const->getAttributes()) . $def . $this->dump($const->getValue(), strlen($def)) . ";\n"; @@ -204,7 +204,7 @@ public function printClass( . ltrim($this->printType($type, $property->isNullable()) . ' ') . '$' . $property->getName()); - $properties[] = Helpers::formatDocComment((string) $property->getComment()) + $properties[] = $this->printDocComment($property) . self::printAttributes($property->getAttributes()) . $def . ($property->getValue() === null && !$property->isInitialized() @@ -242,7 +242,7 @@ public function printClass( : null; $line[] = $class->getName() ? null : '{'; - return Helpers::formatDocComment($class->getComment() . "\n") + return $this->printDocComment($class) . self::printAttributes($class->getAttributes()) . implode(' ', array_filter($line)) . ($class->getName() ? "\n{\n" : "\n") @@ -295,7 +295,7 @@ public function printFile(PhpFile $file): string } return "getComment() ? "\n" . Helpers::formatDocComment($file->getComment() . "\n") : '') + . ($file->getComment() ? "\n" . $this->printDocComment($file) : '') . "\n" . ($file->hasStrictTypes() ? "declare(strict_types=1);\n\n" : '') . implode("\n\n", $namespaces); @@ -332,7 +332,7 @@ protected function printParameters(Closure|GlobalFunction|Method $function, int $type = $param->getType(); $promoted = $param instanceof PromotedParameter ? $param : null; $params[] = - ($promoted ? Helpers::formatDocComment((string) $promoted->getComment()) : '') + ($promoted ? $this->printDocComment($promoted) : '') . ($attrs = self::printAttributes($param->getAttributes(), inline: true)) . ($promoted ? ($promoted->getVisibility() ?: 'public') @@ -375,6 +375,16 @@ protected function printType(?string $type, bool $nullable): string } + protected function printDocComment(/*Traits\CommentAware*/ $commentable): string + { + $multiLine = $commentable instanceof GlobalFunction + || $commentable instanceof Method + || $commentable instanceof ClassLike + || $commentable instanceof PhpFile; + return Helpers::formatDocComment((string) $commentable->getComment(), $multiLine); + } + + private function printReturnType(Closure|GlobalFunction|Method $function): string { return ($tmp = $this->printType($function->getReturnType(), $function->isReturnNullable())) From 8b0d808ff5f19e6a7b290a663dfcc2ac6404dcdd Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 4 Oct 2022 01:49:13 +0200 Subject: [PATCH 138/266] cs --- src/PhpGenerator/Extractor.php | 4 ++-- src/PhpGenerator/Factory.php | 21 +++++++++++---------- src/PhpGenerator/Printer.php | 21 +++++++++++---------- tests/PhpGenerator/Method.returnTypes.phpt | 3 +-- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index d5569f4c..d3846bbf 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -71,7 +71,7 @@ public function extractMethodBodies(string $className): array $res = []; foreach ($nodeFinder->findInstanceOf($classNode, Node\Stmt\ClassMethod::class) as $methodNode) { - /** @var Node\Stmt\ClassMethod $methodNode */ + assert($methodNode instanceof Node\Stmt\ClassMethod); if ($methodNode->stmts) { $res[$methodNode->name->toString()] = $this->getReformattedContents($methodNode->stmts, 2); } @@ -83,11 +83,11 @@ public function extractMethodBodies(string $className): array public function extractFunctionBody(string $name): ?string { - /** @var Node\Stmt\Function_ $functionNode */ $functionNode = (new NodeFinder)->findFirst( $this->statements, fn(Node $node) => $node instanceof Node\Stmt\Function_ && $node->namespacedName->toString() === $name, ); + assert($functionNode instanceof Node\Stmt\Function_); return $this->getReformattedContents($functionNode->stmts, 1); } diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 979ca1c7..58cc4f32 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); } @@ -70,7 +71,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 +161,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 +178,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) { @@ -196,8 +197,8 @@ public function fromCallable(callable $from): Method|GlobalFunction|Closure { $ref = Nette\Utils\Callback::toReflection($from); return $ref instanceof \ReflectionMethod - ? self::fromMethodReflection($ref) - : self::fromFunctionReflection($ref); + ? $this->fromMethodReflection($ref) + : $this->fromFunctionReflection($ref); } @@ -226,7 +227,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 +239,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 +249,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 +266,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/Printer.php b/src/PhpGenerator/Printer.php index 48128454..bcc9c75a 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"; @@ -174,7 +175,7 @@ public function printClass( . 'const ' . $const->getName() . ' = '; $consts[] = $this->printDocComment($const) - . self::printAttributes($const->getAttributes()) + . $this->printAttributes($const->getAttributes()) . $def . $this->dump($const->getValue(), strlen($def)) . ";\n"; } @@ -205,7 +206,7 @@ public function printClass( . '$' . $property->getName()); $properties[] = $this->printDocComment($property) - . self::printAttributes($property->getAttributes()) + . $this->printAttributes($property->getAttributes()) . $def . ($property->getValue() === null && !$property->isInitialized() ? '' @@ -243,7 +244,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 +334,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' : '') 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; From f5299fb67f4738bc611662c831ca43eec63f0671 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 4 Oct 2022 13:38:14 +0200 Subject: [PATCH 139/266] tests: refactoring --- tests/PhpGenerator/Extractor.extractAll.phpt | 52 +------------------- tests/PhpGenerator/expected/Extractor.expect | 22 +++++++++ tests/PhpGenerator/fixtures/extractor.php | 21 ++++++++ 3 files changed, 45 insertions(+), 50 deletions(-) create mode 100644 tests/PhpGenerator/expected/Extractor.expect create mode 100644 tests/PhpGenerator/fixtures/extractor.php diff --git a/tests/PhpGenerator/Extractor.extractAll.phpt b/tests/PhpGenerator/Extractor.extractAll.phpt index 3eccfdd8..fd704ca2 100644 --- a/tests/PhpGenerator/Extractor.extractAll.phpt +++ b/tests/PhpGenerator/Extractor.extractAll.phpt @@ -24,56 +24,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/expected/Extractor.expect b/tests/PhpGenerator/expected/Extractor.expect new file mode 100644 index 00000000..dc56a7e3 --- /dev/null +++ b/tests/PhpGenerator/expected/Extractor.expect @@ -0,0 +1,22 @@ + Date: Tue, 4 Oct 2022 13:22:53 +0200 Subject: [PATCH 140/266] Extractor: keeps the first comment in the method [Closes #119] --- src/PhpGenerator/Extractor.php | 12 +++++++++-- .../expected/ClassType.from.bodies.expect | 2 ++ .../expected/Extractor.bodies.expect | 2 ++ .../Extractor.bodies.resolving.expect | 2 ++ .../Extractor.bodies.unresolving.expect | 2 ++ tests/PhpGenerator/expected/Extractor.expect | 21 +++++++++++++++++++ tests/PhpGenerator/fixtures/extractor.php | 18 ++++++++++++++++ 7 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index d3846bbf..11980a5b 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) { @@ -430,7 +430,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/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.expect b/tests/PhpGenerator/expected/Extractor.expect index dc56a7e3..8b3afc30 100644 --- a/tests/PhpGenerator/expected/Extractor.expect +++ b/tests/PhpGenerator/expected/Extractor.expect @@ -9,6 +9,27 @@ class Class1 } }; } + + + function comment1() + { + /** comment */ + $a = 10; + } + + + function comment2() + { + // comment + "bar"; + } + + + function comment3() + { + // comment + Foo\Bar::XX; + } } /** diff --git a/tests/PhpGenerator/fixtures/extractor.php b/tests/PhpGenerator/fixtures/extractor.php index 2566fac7..fafe8004 100644 --- a/tests/PhpGenerator/fixtures/extractor.php +++ b/tests/PhpGenerator/fixtures/extractor.php @@ -8,6 +8,24 @@ function bar() { } }; } + + function comment1() + { + /** comment */ + $a = 10; + } + + function comment2() + { + // comment + 'bar'; + } + + function comment3() + { + // comment + Foo\Bar::XX; + } } function () {}; From c2732fc4f0bdb1d44749772c5fa1f923de8d43bb Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 4 Oct 2022 02:20:22 +0200 Subject: [PATCH 141/266] Type: constants are PascalCase --- readme.md | 2 +- src/PhpGenerator/Type.php | 51 +++++++++++++------ tests/PhpGenerator/ClassType.phpt | 8 +-- .../PhpGenerator/Method.scalarParameters.phpt | 4 +- tests/PhpGenerator/Type.phpt | 2 +- 5 files changed, 43 insertions(+), 24 deletions(-) diff --git a/readme.md b/readme.md index 8cd138d3..d8909baa 100644 --- a/readme.md +++ b/readme.md @@ -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/Type.php b/src/PhpGenerator/Type.php index be77199d..724e35fb 100644 --- a/src/PhpGenerator/Type.php +++ b/src/PhpGenerator/Type.php @@ -16,22 +16,41 @@ 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', + 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.phpt b/tests/PhpGenerator/ClassType.phpt index 1ec06192..cc6c2a4f 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -60,16 +60,16 @@ $class->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/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/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)); From 7d90395eebc7bea734e1bf84bf7981e0967a42db Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 4 Oct 2022 02:47:24 +0200 Subject: [PATCH 142/266] added Type::True --- src/PhpGenerator/Type.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PhpGenerator/Type.php b/src/PhpGenerator/Type.php index 724e35fb..be3242a4 100644 --- a/src/PhpGenerator/Type.php +++ b/src/PhpGenerator/Type.php @@ -27,6 +27,7 @@ class Type Void = 'void', Never = 'never', Mixed = 'mixed', + True = 'true', False = 'false', Null = 'null', Self = 'self', From 5a40432ec65f1dc4737a934940859bcec67ee669 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 4 Oct 2022 02:31:08 +0200 Subject: [PATCH 143/266] added support for readonly classes --- composer.json | 4 ++-- readme.md | 2 +- src/PhpGenerator/ClassType.php | 14 ++++++++++++++ src/PhpGenerator/Extractor.php | 1 + src/PhpGenerator/Factory.php | 1 + src/PhpGenerator/Printer.php | 1 + tests/PhpGenerator/ClassType.from.82.phpt | 17 +++++++++++++++++ tests/PhpGenerator/Extractor.extractAll.phpt | 3 +++ .../expected/ClassType.from.82.expect | 3 +++ .../expected/Extractor.classes.82.expect | 9 +++++++++ tests/PhpGenerator/fixtures/classes.82.php | 9 +++++++++ 11 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 tests/PhpGenerator/ClassType.from.82.phpt create mode 100644 tests/PhpGenerator/expected/ClassType.from.82.expect create mode 100644 tests/PhpGenerator/expected/Extractor.classes.82.expect create mode 100644 tests/PhpGenerator/fixtures/classes.82.php diff --git a/composer.json b/composer.json index 431cbc9b..9f79b3be 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.14", "tracy/tracy": "^2.8", "phpstan/phpstan": "^1.0" }, diff --git a/readme.md b/readme.md index d8909baa..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()`. ------ 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/Extractor.php b/src/PhpGenerator/Extractor.php index 11980a5b..0be2d2d8 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -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; } diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 58cc4f32..33c6f115 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -56,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(); diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index bcc9c75a..c224ad61 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -227,6 +227,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) { diff --git a/tests/PhpGenerator/ClassType.from.82.phpt b/tests/PhpGenerator/ClassType.from.82.phpt new file mode 100644 index 00000000..5acddad0 --- /dev/null +++ b/tests/PhpGenerator/ClassType.from.82.phpt @@ -0,0 +1,17 @@ +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); diff --git a/tests/PhpGenerator/expected/ClassType.from.82.expect b/tests/PhpGenerator/expected/ClassType.from.82.expect new file mode 100644 index 00000000..39bb4484 --- /dev/null +++ b/tests/PhpGenerator/expected/ClassType.from.82.expect @@ -0,0 +1,3 @@ +readonly class Class13 +{ +} diff --git a/tests/PhpGenerator/expected/Extractor.classes.82.expect b/tests/PhpGenerator/expected/Extractor.classes.82.expect new file mode 100644 index 00000000..ea812871 --- /dev/null +++ b/tests/PhpGenerator/expected/Extractor.classes.82.expect @@ -0,0 +1,9 @@ + Date: Tue, 4 Oct 2022 02:56:46 +0200 Subject: [PATCH 144/266] added support for constants in traits --- src/PhpGenerator/Printer.php | 16 +++++++--------- src/PhpGenerator/TraitType.php | 3 +++ tests/PhpGenerator/ClassType.from.82.phpt | 1 + .../expected/ClassType.from.82.expect | 5 +++++ .../expected/Extractor.classes.82.expect | 5 +++++ tests/PhpGenerator/fixtures/classes.82.php | 6 ++++++ 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index c224ad61..c7005fe1 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -168,7 +168,13 @@ 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() . ' ' : '') @@ -179,15 +185,7 @@ public function printClass( . $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()); } 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/tests/PhpGenerator/ClassType.from.82.phpt b/tests/PhpGenerator/ClassType.from.82.phpt index 5acddad0..db001ac7 100644 --- a/tests/PhpGenerator/ClassType.from.82.phpt +++ b/tests/PhpGenerator/ClassType.from.82.phpt @@ -13,5 +13,6 @@ require __DIR__ . '/../bootstrap.php'; require __DIR__ . '/fixtures/classes.82.php'; $res[] = ClassType::from(new Abc\Class13); +$res[] = ClassType::from(Abc\Trait13::class); sameFile(__DIR__ . '/expected/ClassType.from.82.expect', implode("\n", $res)); diff --git a/tests/PhpGenerator/expected/ClassType.from.82.expect b/tests/PhpGenerator/expected/ClassType.from.82.expect index 39bb4484..4c03d053 100644 --- a/tests/PhpGenerator/expected/ClassType.from.82.expect +++ b/tests/PhpGenerator/expected/ClassType.from.82.expect @@ -1,3 +1,8 @@ readonly class Class13 { } + +trait Trait13 +{ + public const FOO = 123; +} diff --git a/tests/PhpGenerator/expected/Extractor.classes.82.expect b/tests/PhpGenerator/expected/Extractor.classes.82.expect index ea812871..c7de3d7e 100644 --- a/tests/PhpGenerator/expected/Extractor.classes.82.expect +++ b/tests/PhpGenerator/expected/Extractor.classes.82.expect @@ -7,3 +7,8 @@ namespace Abc; readonly class Class13 { } + +trait Trait13 +{ + public const FOO = 123; +} diff --git a/tests/PhpGenerator/fixtures/classes.82.php b/tests/PhpGenerator/fixtures/classes.82.php index ea812871..95e594ff 100644 --- a/tests/PhpGenerator/fixtures/classes.82.php +++ b/tests/PhpGenerator/fixtures/classes.82.php @@ -7,3 +7,9 @@ readonly class Class13 { } + + +trait Trait13 +{ + public const FOO = 123; +} From 7f6270b5a1b0bb7ffcc9702a42a098ce10bbc53e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 4 Oct 2022 03:07:10 +0200 Subject: [PATCH 145/266] added support for DNF types --- composer.json | 2 +- src/PhpGenerator/Helpers.php | 12 ++++-- tests/PhpGenerator/ClassType.phpt | 4 +- tests/PhpGenerator/Helpers.validateType.phpt | 43 +++++++++++++++++++ .../expected/ClassType.from.82.expect | 3 ++ .../expected/Extractor.classes.82.expect | 3 ++ tests/PhpGenerator/fixtures/classes.82.php | 3 ++ 7 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 tests/PhpGenerator/Helpers.validateType.phpt diff --git a/composer.json b/composer.json index 9f79b3be..fc193a79 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ }, "require-dev": { "nette/tester": "^2.4", - "nikic/php-parser": "^4.14", + "nikic/php-parser": "^4.15", "tracy/tracy": "^2.8", "phpstan/phpstan": "^1.0" }, diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 71e7de6f..e49db879 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -174,10 +174,14 @@ public static function validateType(?string $type, bool &$nullable): ?string 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/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index cc6c2a4f..86fc78ef 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -131,8 +131,8 @@ $method->addParameter('res', null) ->setType(Type::union(Type::Array, 'null')); $method->addParameter('bar', null) - ->setType('stdClass|string') - ->setNullable(true); + ->setNullable(true) + ->setType('stdClass|string'); $class->addTrait('foo'); $class->removeTrait('foo'); 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/expected/ClassType.from.82.expect b/tests/PhpGenerator/expected/ClassType.from.82.expect index 4c03d053..6ea6378a 100644 --- a/tests/PhpGenerator/expected/ClassType.from.82.expect +++ b/tests/PhpGenerator/expected/ClassType.from.82.expect @@ -1,5 +1,8 @@ readonly class Class13 { + public function func(C|(X&D)|null $foo): (A&B)|null + { + } } trait Trait13 diff --git a/tests/PhpGenerator/expected/Extractor.classes.82.expect b/tests/PhpGenerator/expected/Extractor.classes.82.expect index c7de3d7e..54f5c525 100644 --- a/tests/PhpGenerator/expected/Extractor.classes.82.expect +++ b/tests/PhpGenerator/expected/Extractor.classes.82.expect @@ -6,6 +6,9 @@ namespace Abc; readonly class Class13 { + public function func(C|(X&D)|null $foo): (A&B)|null + { + } } trait Trait13 diff --git a/tests/PhpGenerator/fixtures/classes.82.php b/tests/PhpGenerator/fixtures/classes.82.php index 95e594ff..ae22c11e 100644 --- a/tests/PhpGenerator/fixtures/classes.82.php +++ b/tests/PhpGenerator/fixtures/classes.82.php @@ -6,6 +6,9 @@ readonly class Class13 { + public function func(C|(X&D)|null $foo): (A&B)|null + { + } } From b0f47e52766bb0a8cfd7f01b33b69e79f83be823 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 4 Oct 2022 14:14:48 +0200 Subject: [PATCH 146/266] updated github workflow --- .github/workflows/coding-style.yml | 8 ++++---- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) 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 From a8d6abeae5d8c7202cd69600e086a7a72877fc86 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 6 Oct 2022 23:13:58 +0200 Subject: [PATCH 147/266] PhpNamespace: better use-statements sorting behavior --- src/PhpGenerator/PhpNamespace.php | 2 +- tests/PhpGenerator/Printer.use-order.phpt | 28 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/PhpGenerator/Printer.use-order.phpt diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 890fc8bf..660ff026 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 => strtr($a, '\\', ' ') <=> strtr($b, '\\', ' ')); return array_filter( $this->aliases[$of], fn($name, $alias) => strcasecmp(($this->name ? $this->name . '\\' : '') . $alias, $name), 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), +); From daab6a65bed5ff5edc8962b44b13cfe2c43325e9 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 20 Dec 2022 01:13:22 +0100 Subject: [PATCH 148/266] updated .gitattributes --- .gitattributes | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitattributes b/.gitattributes index 3aa6270a..a728f976 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,9 +1,8 @@ .gitattributes export-ignore .gitignore export-ignore .github export-ignore -.travis.yml export-ignore -ecs.php export-ignore -phpstan.neon export-ignore +ncs.* export-ignore +phpstan*.neon export-ignore tests/ export-ignore *.sh eol=lf From 6886892ecd7ca093f8aa4cc0463b5ba843a18744 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 5 Dec 2022 01:55:53 +0100 Subject: [PATCH 149/266] better deprecation messages --- src/PhpGenerator/ClassLike.php | 13 ++++--- src/PhpGenerator/PhpNamespace.php | 13 ++++--- src/PhpGenerator/Type.php | 65 ++++++++++++++++++++++--------- 3 files changed, 63 insertions(+), 28 deletions(-) diff --git a/src/PhpGenerator/ClassLike.php b/src/PhpGenerator/ClassLike.php index 8df107dc..6d9c1e8b 100644 --- a/src/PhpGenerator/ClassLike.php +++ b/src/PhpGenerator/ClassLike.php @@ -26,11 +26,14 @@ abstract class ClassLike VisibilityProtected = 'protected', VisibilityPrivate = 'private'; - /** @deprecated */ - public const - VISIBILITY_PUBLIC = self::VisibilityPublic, - VISIBILITY_PROTECTED = self::VisibilityProtected, - VISIBILITY_PRIVATE = self::VisibilityPrivate; + /** @deprecated use ClassLike::VisibilityPublic */ + public const VISIBILITY_PUBLIC = self::VisibilityPublic; + + /** @deprecated use ClassLike::VisibilityProtected */ + public const VISIBILITY_PROTECTED = self::VisibilityProtected; + + /** @deprecated use ClassLike::VisibilityPrivate */ + public const VISIBILITY_PRIVATE = self::VisibilityPrivate; private ?PhpNamespace $namespace; private ?string $name; diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 660ff026..0303d2dd 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -30,11 +30,14 @@ final class PhpNamespace NameFunction = 'f', NameConstant = 'c'; - /** @deprecated */ - public const - NAME_NORMAL = self::NameNormal, - NAME_FUNCTION = self::NameFunction, - NAME_CONSTANT = self::NameConstant; + /** @deprecated use PhpNamespace::NameNormal */ + public const NAME_NORMAL = self::NameNormal; + + /** @deprecated use PhpNamespace::NameFunction */ + public const NAME_FUNCTION = self::NameFunction; + + /** @deprecated use PhpNamespace::NameConstant */ + public const NAME_CONSTANT = self::NameConstant; private string $name; diff --git a/src/PhpGenerator/Type.php b/src/PhpGenerator/Type.php index be3242a4..9abd87cd 100644 --- a/src/PhpGenerator/Type.php +++ b/src/PhpGenerator/Type.php @@ -34,24 +34,53 @@ class Type 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; + /** @deprecated use Type::String */ + public const STRING = self::String; + + /** @deprecated use Type::Int */ + public const INT = self::Int; + + /** @deprecated use Type::Float */ + public const FLOAT = self::Float; + + /** @deprecated use Type::Bool */ + public const BOOL = self::Bool; + + /** @deprecated use Type::Array */ + public const ARRAY = self::Array; + + /** @deprecated use Type::Object */ + public const OBJECT = self::Object; + + /** @deprecated use Type::Callable */ + public const CALLABLE = self::Callable; + + /** @deprecated use Type::Iterable */ + public const ITERABLE = self::Iterable; + + /** @deprecated use Type::Void */ + public const VOID = self::Void; + + /** @deprecated use Type::Never */ + public const NEVER = self::Never; + + /** @deprecated use Type::Mixed */ + public const MIXED = self::Mixed; + + /** @deprecated use Type::False */ + public const FALSE = self::False; + + /** @deprecated use Type::Null */ + public const NULL = self::Null; + + /** @deprecated use Type::Self */ + public const SELF = self::Self; + + /** @deprecated use Type::Parent */ + public const PARENT = self::Parent; + + /** @deprecated use Type::Static */ + public const STATIC = self::Static; public static function nullable(string $type, bool $state = true): string From 70acf621adbb27f97d87003d421f1892ba278dd2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 17 Jan 2023 21:01:00 +0100 Subject: [PATCH 150/266] typo --- src/PhpGenerator/Helpers.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index e49db879..3cd4a10c 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -25,7 +25,7 @@ final class Helpers // built-in types 'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1, 'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, 'mixed' => 1, 'false' => 1, - 'never' => 1, + 'never' => 1, 'true' => 1, // class keywords 'self' => 1, 'parent' => 1, 'static' => 1, @@ -42,9 +42,6 @@ final class Helpers '__TRAIT__' => 1, '__FUNCTION__' => 1, '__METHOD__' => 1, '__LINE__' => 1, '__FILE__' => 1, '__DIR__' => 1, '__NAMESPACE__' => 1, 'fn' => 1, 'match' => 1, 'enum' => 1, 'abstract' => 1, 'final' => 1, 'private' => 1, 'protected' => 1, 'public' => 1, 'readonly' => 1, - - // additional reserved class names - 'true' => 1, ]; /** @deprecated */ From e9f713c9a03cf07b420181ffe32f17c23123ffb6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 10 Jan 2023 01:34:14 +0100 Subject: [PATCH 151/266] used PhpStorm Language attribute --- composer.json | 3 ++- src/PhpGenerator/Traits/FunctionLike.php | 13 +++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index fc193a79..25dac358 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "nette/tester": "^2.4", "nikic/php-parser": "^4.15", "tracy/tracy": "^2.8", - "phpstan/phpstan": "^1.0" + "phpstan/phpstan": "^1.0", + "jetbrains/phpstorm-attributes": "dev-master" }, "suggest": { "nikic/php-parser": "to use ClassType::from(withBodies: true) & ClassType::fromCode()" diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index 15bb8e57..c0b33f99 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -9,6 +9,7 @@ namespace Nette\PhpGenerator\Traits; +use JetBrains\PhpStorm\Language; use Nette; use Nette\PhpGenerator\Dumper; use Nette\PhpGenerator\Parameter; @@ -31,7 +32,11 @@ trait FunctionLike /** @param ?mixed[] $args */ - public function setBody(string $code, ?array $args = null): static + public function setBody( + #[Language('PHP')] + string $code, + ?array $args = null, + ): static { $this->body = $args === null ? $code @@ -47,7 +52,11 @@ public function getBody(): string /** @param ?mixed[] $args */ - public function addBody(string $code, ?array $args = null): static + public function addBody( + #[Language('PHP')] + string $code, + ?array $args = null, + ): static { $this->body .= ($args === null ? $code : (new Dumper)->format($code, ...$args)) . "\n"; return $this; From 2d9e6a4a07bfc2359f322765cae0528d6397ef22 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 7 Feb 2023 08:39:23 +0100 Subject: [PATCH 152/266] Extractor: refactoring --- src/PhpGenerator/Extractor.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 0be2d2d8..f0f2abab 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -314,7 +314,7 @@ private function addPropertyToClass(ClassLike $class, Node\Stmt\Property $node): $prop->setVisibility($this->toVisibility($node->flags)); $prop->setType($node->type ? $this->toPhp($node->type) : null); if ($item->default) { - $prop->setValue(new Literal($this->getReformattedContents([$item->default], 1))); + $prop->setValue($this->formatValue($item->default, 1)); } $prop->setReadOnly(method_exists($node, 'isReadonly') && $node->isReadonly()); @@ -337,8 +337,7 @@ private function addMethodToClass(ClassLike $class, Node\Stmt\ClassMethod $node) private function addConstantToClass(ClassLike $class, Node\Stmt\ClassConst $node): void { foreach ($node->consts as $item) { - $value = $this->getReformattedContents([$item->value], 1); - $const = $class->addConstant($item->name->toString(), new Literal($value)); + $const = $class->addConstant($item->name->toString(), $this->formatValue($item->value, 1)); $const->setVisibility($this->toVisibility($node->flags)); $const->setFinal(method_exists($node, 'isFinal') && $node->isFinal()); $this->addCommentAndAttributes($const, $node); @@ -351,7 +350,7 @@ private function addEnumCaseToClass(EnumType $class, Node\Stmt\EnumCase $node): $value = match (true) { $node->expr === null => null, $node->expr instanceof Node\Scalar\LNumber, $node->expr instanceof Node\Scalar\String_ => $node->expr->value, - default => new Literal($this->getReformattedContents([$node->expr], 1)), + default => $this->formatValue($node->expr, 1), }; $case = $class->addCase($node->name->toString(), $value); $this->addCommentAndAttributes($case, $node); @@ -371,7 +370,7 @@ private function addCommentAndAttributes($element, Node $node): void foreach ($group->attrs as $attribute) { $args = []; foreach ($attribute->args as $arg) { - $value = new Literal($this->getReformattedContents([$arg->value], 0)); + $value = $this->formatValue($arg->value, 0); if ($arg->name) { $args[$arg->name->toString()] = $value; } else { @@ -399,7 +398,7 @@ private function setupFunction(GlobalFunction|Method $function, Node\FunctionLik $param->setReference($item->byRef); $function->setVariadic($item->variadic); if ($item->default) { - $param->setDefaultValue(new Literal($this->getReformattedContents([$item->default], 2))); + $param->setDefaultValue($this->formatValue($item->default, 2)); } $this->addCommentAndAttributes($param, $item); @@ -412,6 +411,13 @@ private function setupFunction(GlobalFunction|Method $function, Node\FunctionLik } + private function formatValue(Node\Expr $value, int $level): Literal + { + $value = $this->getReformattedContents([$value], $level); + return new Literal($value); + } + + private function toVisibility(int $flags): ?string { return match (true) { From c4e433ff00369a2100393de3f4f1e00a8e4d8ecd Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 13 Mar 2023 16:17:53 +0100 Subject: [PATCH 153/266] Printer: printAttributes & printReturnType are protected [Closes #123] --- src/PhpGenerator/Printer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index c7005fe1..115a39be 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -385,7 +385,7 @@ protected function printDocComment(/*Traits\CommentAware*/ $commentable): string } - private function printReturnType(Closure|GlobalFunction|Method $function): string + protected function printReturnType(Closure|GlobalFunction|Method $function): string { return ($tmp = $this->printType($function->getReturnType(), $function->isReturnNullable())) ? $this->returnTypeColon . $tmp @@ -394,7 +394,7 @@ private function printReturnType(Closure|GlobalFunction|Method $function): strin /** @param Attribute[] $attrs */ - private function printAttributes(array $attrs, bool $inline = false): string + protected function printAttributes(array $attrs, bool $inline = false): string { if (!$attrs) { return ''; From cd40df575bf84cef0393c31b8493b7ecfc6e3a82 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 13 Mar 2023 15:29:58 +0100 Subject: [PATCH 154/266] Printer: promoted parameters / parameter with attributes are always multiline --- src/PhpGenerator/Printer.php | 7 ++++--- tests/PhpGenerator/expected/ClassType.attributes.expect | 5 +++-- tests/PhpGenerator/expected/ClassType.from.expect | 5 +++-- tests/PhpGenerator/expected/Extractor.classes.81.expect | 5 +++-- tests/PhpGenerator/expected/Extractor.classes.expect | 5 +++-- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 115a39be..b818b60d 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -324,7 +324,7 @@ protected function printParameters(Closure|GlobalFunction|Method $function, int { $params = []; $list = $function->getParameters(); - $special = false; + $multiline = false; foreach ($list as $param) { $param->validate(); @@ -344,12 +344,13 @@ protected function printParameters(Closure|GlobalFunction|Method $function, int . '$' . $param->getName() . ($param->hasDefaultValue() && !$variadic ? ' = ' . $this->dump($param->getDefaultValue()) : ''); - $special = $special || $promoted || $attrs; + $multiline = $multiline || $promoted || $attrs; } $line = implode(', ', $params); + $multiline = $multiline || count($params) > 1 && (strlen($line) + $column > $this->wrapLength); - return count($params) > 1 && ($special || strlen($line) + $column > $this->wrapLength) + return $multiline ? "(\n" . $this->indent(implode(",\n", $params)) . ",\n)" : "($line)"; } diff --git a/tests/PhpGenerator/expected/ClassType.attributes.expect b/tests/PhpGenerator/expected/ClassType.attributes.expect index b10df65e..57d6307a 100644 --- a/tests/PhpGenerator/expected/ClassType.attributes.expect +++ b/tests/PhpGenerator/expected/ClassType.attributes.expect @@ -20,7 +20,8 @@ class Example * Returns file handle. */ #[ExampleAttribute] - public function getHandle(#[ExampleAttribute, WithArguments(123)] $mode) - { + public function getHandle( + #[ExampleAttribute, WithArguments(123)] $mode, + ) { } } diff --git a/tests/PhpGenerator/expected/ClassType.from.expect b/tests/PhpGenerator/expected/ClassType.from.expect index b52d045e..f66a53a9 100644 --- a/tests/PhpGenerator/expected/ClassType.from.expect +++ b/tests/PhpGenerator/expected/ClassType.from.expect @@ -138,8 +138,9 @@ class Class9 * Returns file handle */ #[ExampleAttribute] - public function getHandle(#[WithArguments(123)] $mode) - { + public function getHandle( + #[WithArguments(123)] $mode, + ) { } } diff --git a/tests/PhpGenerator/expected/Extractor.classes.81.expect b/tests/PhpGenerator/expected/Extractor.classes.81.expect index 831e8e31..1ace4741 100644 --- a/tests/PhpGenerator/expected/Extractor.classes.81.expect +++ b/tests/PhpGenerator/expected/Extractor.classes.81.expect @@ -33,8 +33,9 @@ class Class12 private readonly string $bar; - public function __construct(private readonly string $foo) - { + public function __construct( + private readonly string $foo, + ) { $this->bar = "foobar"; } } diff --git a/tests/PhpGenerator/expected/Extractor.classes.expect b/tests/PhpGenerator/expected/Extractor.classes.expect index 2ee92a2a..25d94e84 100644 --- a/tests/PhpGenerator/expected/Extractor.classes.expect +++ b/tests/PhpGenerator/expected/Extractor.classes.expect @@ -148,8 +148,9 @@ class Class9 * Returns file handle */ #[ExampleAttribute] - public function getHandle(#[WithArguments(123)] $mode) - { + public function getHandle( + #[WithArguments(123)] $mode, + ) { } } From 43d6101747cb46457c5fd1d47f580acc0fb65ceb Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 13 Mar 2023 15:48:16 +0100 Subject: [PATCH 155/266] Printer: print brace on next line when method/function has typehint --- src/PhpGenerator/Printer.php | 8 ++- tests/PhpGenerator/Printer.function.phpt | 55 +++++++++++++++++++ tests/PhpGenerator/Printer.phpt | 20 +++---- .../PhpGenerator/expected/PhpNamespace.expect | 3 +- .../expected/Printer.class-alt.expect | 14 +++++ .../expected/Printer.class.expect | 13 +++++ .../expected/Printer.function.expect | 5 -- 7 files changed, 98 insertions(+), 20 deletions(-) create mode 100644 tests/PhpGenerator/Printer.function.phpt delete mode 100644 tests/PhpGenerator/expected/Printer.function.expect diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index b818b60d..d3ef850d 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -45,15 +45,17 @@ public function printFunction(GlobalFunction $function, ?PhpNamespace $namespace . ($function->getReturnReference() ? '&' : '') . $function->getName(); $returnType = $this->printReturnType($function); + $params = $this->printParameters($function, strlen($line) + strlen($returnType) + 2); // 2 = parentheses $body = Helpers::simplifyTaggedNames($function->getBody(), $this->namespace); $body = ltrim(rtrim(Strings::normalize($body)) . "\n"); + $braceOnNextLine = $this->bracesOnNextLine && (!str_contains($params, "\n") || $returnType); return $this->printDocComment($function) . $this->printAttributes($function->getAttributes()) . $line - . $this->printParameters($function, strlen($line) + strlen($returnType) + 2) // 2 = parentheses + . $params . $returnType - . ($this->bracesOnNextLine ? "\n" : ' ') + . ($braceOnNextLine ? "\n" : ' ') . "{\n" . $this->indent($body) . "}\n"; } @@ -117,7 +119,7 @@ public function printMethod(Method $method, ?PhpNamespace $namespace = null, boo $params = $this->printParameters($method, strlen($line) + strlen($returnType) + strlen($this->indentation) + 2); $body = Helpers::simplifyTaggedNames($method->getBody(), $this->namespace); $body = ltrim(rtrim(Strings::normalize($body)) . "\n"); - $braceOnNextLine = $this->bracesOnNextLine && !str_contains($params, "\n"); + $braceOnNextLine = $this->bracesOnNextLine && (!str_contains($params, "\n") || $returnType); return $this->printDocComment($method) . $this->printAttributes($method->getAttributes()) diff --git a/tests/PhpGenerator/Printer.function.phpt b/tests/PhpGenerator/Printer.function.phpt new file mode 100644 index 00000000..f9ed7719 --- /dev/null +++ b/tests/PhpGenerator/Printer.function.phpt @@ -0,0 +1,55 @@ +setReturnType('stdClass') + ->setBody("func(); \r\nreturn 123;") + ->addParameter('var') + ->setType('stdClass'); + +Assert::match(<<<'XX' + function func(stdClass $var): stdClass + { + func(); + return 123; + } + + XX, $printer->printFunction($function)); + + +$function = new Nette\PhpGenerator\GlobalFunction('multi'); +$function->addParameter('foo') + ->addAttribute('Foo'); + +Assert::match(<<<'XX' + function multi( + #[Foo] $foo, + ) { + } + + XX, $printer->printFunction($function)); + + +$function = new Nette\PhpGenerator\GlobalFunction('multiType'); +$function + ->setReturnType('array') + ->addParameter('foo') + ->addAttribute('Foo'); + +Assert::match(<<<'XX' + function multiType( + #[Foo] $foo, + ): array + { + } + + XX, $printer->printFunction($function)); diff --git a/tests/PhpGenerator/Printer.phpt b/tests/PhpGenerator/Printer.phpt index 5a8cd996..efecd0b5 100644 --- a/tests/PhpGenerator/Printer.phpt +++ b/tests/PhpGenerator/Printer.phpt @@ -51,6 +51,15 @@ $class->addMethod('first') $class->addMethod('second'); +$method = $class->addMethod('multi') + ->addParameter('foo') + ->addAttribute('Foo'); + +$method = $class->addMethod('multiType') + ->setReturnType('array') + ->addParameter('foo') + ->addAttribute('Foo'); + sameFile(__DIR__ . '/expected/Printer.class.expect', $printer->printClass($class)); sameFile(__DIR__ . '/expected/Printer.method.expect', $printer->printMethod($class->getMethod('first'))); @@ -63,17 +72,6 @@ sameFile(__DIR__ . '/expected/Printer.class-alt.expect', $printer->printClass($c -$printer = new Printer; -$function = new Nette\PhpGenerator\GlobalFunction('func'); -$function - ->setReturnType('stdClass') - ->setBody("func(); \r\nreturn 123;") - ->addParameter('var') - ->setType('stdClass'); - -sameFile(__DIR__ . '/expected/Printer.function.expect', $printer->printFunction($function)); - - $closure = new Nette\PhpGenerator\Closure; $closure ->setReturnType('stdClass') diff --git a/tests/PhpGenerator/expected/PhpNamespace.expect b/tests/PhpGenerator/expected/PhpNamespace.expect index 23e26d2b..0577d9c4 100644 --- a/tests/PhpGenerator/expected/PhpNamespace.expect +++ b/tests/PhpGenerator/expected/PhpNamespace.expect @@ -18,7 +18,8 @@ class A implements A, C array $d, ?callable $e, C|string $f, - ): static|A { + ): static|A + { } } diff --git a/tests/PhpGenerator/expected/Printer.class-alt.expect b/tests/PhpGenerator/expected/Printer.class-alt.expect index ee340d14..5fa5b6ff 100644 --- a/tests/PhpGenerator/expected/Printer.class-alt.expect +++ b/tests/PhpGenerator/expected/Printer.class-alt.expect @@ -60,4 +60,18 @@ final class Example extends ParentClass implements IExample public function second() { } + + + + public function multi( + #[Foo] $foo, + ) { + } + + + + public function multiType( + #[Foo] $foo, + ): array { + } } diff --git a/tests/PhpGenerator/expected/Printer.class.expect b/tests/PhpGenerator/expected/Printer.class.expect index dbdc302b..c66082ba 100644 --- a/tests/PhpGenerator/expected/Printer.class.expect +++ b/tests/PhpGenerator/expected/Printer.class.expect @@ -59,4 +59,17 @@ final class Example extends ParentClass implements IExample public function second() { } + + + public function multi( + #[Foo] $foo, + ) { + } + + + public function multiType( + #[Foo] $foo, + ): array + { + } } diff --git a/tests/PhpGenerator/expected/Printer.function.expect b/tests/PhpGenerator/expected/Printer.function.expect deleted file mode 100644 index 45cade76..00000000 --- a/tests/PhpGenerator/expected/Printer.function.expect +++ /dev/null @@ -1,5 +0,0 @@ -function func(stdClass $var): stdClass -{ - func(); - return 123; -} From b9ca0b4564286de33df05b969a28bdf6087b1fa8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 13 Mar 2023 18:18:33 +0100 Subject: [PATCH 156/266] Helpers: uses Validator::isTypeDeclaration() --- composer.json | 2 +- src/PhpGenerator/Helpers.php | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index 25dac358..e8dda3c2 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ ], "require": { "php": ">=8.0 <8.3", - "nette/utils": "^3.2.7 || ^4.0" + "nette/utils": "^3.2.9 || ^4.0" }, "require-dev": { "nette/tester": "^2.4", diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 3cd4a10c..0439bcb2 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -169,16 +169,7 @@ public static function validateType(?string $type, bool &$nullable): ?string { if ($type === '' || $type === null) { return null; - } - - if (!preg_match(<<<'XX' - ~(?n) - ( - \?? (? [\w\\]+)| - (? (?&type) (& (?&type))+ )| - (? (?&type) | \( (?&intersection) \) ) (\| (?&upart) )+ - )$~xAD - XX, $type)) { + } elseif (!Nette\Utils\Validators::isTypeDeclaration($type)) { throw new Nette\InvalidArgumentException("Value '$type' is not valid type."); } From 0f1275bb8d39b3eb92b57c22a51fe693f1f145a5 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 13 Mar 2023 18:23:26 +0100 Subject: [PATCH 157/266] added support for typed constants --- readme.md | 3 ++- src/PhpGenerator/Constant.php | 14 ++++++++++++++ src/PhpGenerator/Helpers.php | 2 +- src/PhpGenerator/Printer.php | 4 +++- tests/PhpGenerator/ClassType.phpt | 3 ++- tests/PhpGenerator/expected/ClassType.expect | 2 +- 6 files changed, 23 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index 01f74159..54036174 100644 --- a/readme.md +++ b/readme.md @@ -72,6 +72,7 @@ We can add constants (class [Constant](https://api.nette.org/php-generator/maste ```php $class->addConstant('ID', 123) ->setProtected() // constant visiblity + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -87,7 +88,7 @@ $class->addProperty('list') It generates: ```php -final protected const ID = 123; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; diff --git a/src/PhpGenerator/Constant.php b/src/PhpGenerator/Constant.php index 188b19cd..8097dba5 100644 --- a/src/PhpGenerator/Constant.php +++ b/src/PhpGenerator/Constant.php @@ -25,6 +25,7 @@ final class Constant private mixed $value; private bool $final = false; + private ?string $type = null; public function setValue(mixed $val): static @@ -51,4 +52,17 @@ public function isFinal(): bool { return $this->final; } + + + public function setType(?string $type): static + { + $this->type = Helpers::validateType($type); + return $this; + } + + + public function getType(): ?string + { + return $this->type; + } } diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 0439bcb2..fc3e1c3f 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -165,7 +165,7 @@ public static function createObject(string $class, array $props): object } - public static function validateType(?string $type, bool &$nullable): ?string + public static function validateType(?string $type, bool &$nullable = false): ?string { if ($type === '' || $type === null) { return null; diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index d3ef850d..4731fbe3 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -180,7 +180,9 @@ public function printClass( foreach ($class->getConstants() as $const) { $def = ($const->isFinal() ? 'final ' : '') . ($const->getVisibility() ? $const->getVisibility() . ' ' : '') - . 'const ' . $const->getName() . ' = '; + . 'const ' + . ltrim($this->printType($const->getType(), nullable: false) . ' ') + . $const->getName() . ' = '; $consts[] = $this->printDocComment($const) . $this->printAttributes($const->getAttributes()) diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index 86fc78ef..2c925281 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -39,7 +39,8 @@ $trait2 = $class->addTrait('AnotherTrait') $class->addConstant('ROLE', 'admin'); $class->addConstant('ACTIVE', false) - ->setFinal(); + ->setFinal() + ->setType('bool'); Assert::false($class->isFinal()); Assert::true($class->isAbstract()); diff --git a/tests/PhpGenerator/expected/ClassType.expect b/tests/PhpGenerator/expected/ClassType.expect index 1be91229..ccd736b2 100644 --- a/tests/PhpGenerator/expected/ClassType.expect +++ b/tests/PhpGenerator/expected/ClassType.expect @@ -17,7 +17,7 @@ abstract class Example extends ParentClass implements IExample, IOne } public const ROLE = 'admin'; - final public const ACTIVE = false; + final public const bool ACTIVE = false; /** Commented */ private const FORCE_ARRAY = Nette\Utils\Json::FORCE_ARRAY; From 34aaf23f70d86ac550396998e851aae6a2ed20b4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 23 Mar 2023 17:32:04 +0100 Subject: [PATCH 158/266] readme: improved info about loading from file [Closes #129] --- readme.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 54036174..81478652 100644 --- a/readme.md +++ b/readme.md @@ -766,7 +766,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); Loading from PHP File --------------------- -You can also load classes and functions directly from a PHP file that is not already loaded or string of PHP code: +You can also load functions, classes, interfaces and enums directly from a string of PHP code. For example, we create `ClassType` object this way: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<< Date: Thu, 23 Mar 2023 17:44:37 +0100 Subject: [PATCH 159/266] readme: improvements --- readme.md | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/readme.md b/readme.md index 81478652..d6520a56 100644 --- a/readme.md +++ b/readme.md @@ -36,7 +36,8 @@ Let's start with a straightforward example of generating class using [ClassType] ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->setFinal() +$class + ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) ->addComment("Description of class.\nSecond line\n") @@ -171,6 +172,7 @@ $methodRecount = $methodCount->cloneWithName('recount'); $class->addMember($methodRecount); ``` + Interface or Trait ------------------ @@ -267,6 +269,7 @@ $obj = new class ($val) { }; ``` + Global Function --------------- @@ -292,6 +295,7 @@ function foo($a, $b) } ``` + Closure ------- @@ -318,6 +322,7 @@ function ($a, $b) use (&$c) { } ``` + Arrow Function -------------- @@ -338,6 +343,7 @@ Result: fn($a, $b) => $a + $b ``` + Method and Function Signature ----------------------------- @@ -377,6 +383,7 @@ function count(...$items) } ``` + Method and Function Body ------------------------ @@ -466,7 +473,8 @@ function foo($a) } ``` -Printers and PSR compliance + +Printers and PSR Compliance --------------------------- PHP code is generated by `Printer` objects. There is a `PsrPrinter` whose output conforms to PSR-2 and PSR-12 and uses spaces for indentation, and a `Printer` that uses tabs for indentation. @@ -484,12 +492,19 @@ Need to customize printer behavior? Create your own by inheriting the `Printer` ```php class MyPrinter extends Nette\PhpGenerator\Printer { + /** length of the line after which the line will break */ public int $wrapLength = 120; + /** indentation character, can be replaced with a sequence of spaces */ public string $indentation = "\t"; + /** number of blank lines between properties */ public int $linesBetweenProperties = 0; + /** number of blank lines between methods */ public int $linesBetweenMethods = 2; + /** number of blank lines between groups of use statements for classes, functions, and constants */ public int $linesBetweenUseTypes = 0; + /** position of the opening brace for functions and methods */ public bool $bracesOnNextLine = true; + /** separator between the right parenthesis and return type of functions and methods */ public string $returnTypeColon = ': '; } ``` @@ -551,11 +566,10 @@ new Literal('substr(?, ?)', [$a, $b]); ``` - Attributes ---------- -You can add PHP 8 attributes to all classes, methods, properties, constants, enum cases, functions, closures and parameters (class [Attribute](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Attribute.html)). +You can add PHP 8 attributes to all classes, methods, properties, constants, enum cases, functions, closures and parameters. [Literals](#literals) can also be used as parameter values. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -590,6 +604,7 @@ class Demo } ``` + Namespace --------- @@ -635,13 +650,13 @@ echo $namespace->resolveName('Bar'); // 'Foo\Bar' echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` + Class Names Resolving --------------------- **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, used traits and attributes) 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: +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'); @@ -690,6 +705,7 @@ $printer->setTypeResolving(false); echo $printer->printNamespace($namespace); ``` + PHP Files --------- @@ -736,6 +752,7 @@ function foo() } ``` + Generating According to Existing Ones ------------------------------------- @@ -750,7 +767,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('trim'); // creates a closure as specified $closure = Nette\PhpGenerator\Closure::from( - function (stdClass $a, $b = null) {} + function (stdClass $a, $b = null) {}, ); ``` @@ -763,6 +780,7 @@ $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` + Loading from PHP File --------------------- @@ -797,7 +815,7 @@ This requires `nikic/php-parser` to be installed. Variables Dumper ---------------- -The Dumper returns a parsable PHP string representation of a variable. Provides better and clearer output that native functon `var_export()`. +The Dumper returns a parsable PHP string representation of a variable. Provides better and clearer output that native function `var_export()`. ```php $dumper = new Nette\PhpGenerator\Dumper; From 52d62be16739fe5dd9f63e463f433ad0de4dbd72 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 26 Apr 2023 13:06:35 +0200 Subject: [PATCH 160/266] coding style --- tests/PhpGenerator/ClassType.addMember.phpt | 8 +- tests/PhpGenerator/ClassType.from.bodies.phpt | 10 +- tests/PhpGenerator/ClassType.phpt | 63 +-- tests/PhpGenerator/ClassType.validate.phpt | 6 +- tests/PhpGenerator/Closure.from.phpt | 6 +- tests/PhpGenerator/Closure.long.phpt | 76 ++-- tests/PhpGenerator/Closure.phpt | 36 +- tests/PhpGenerator/Dumper.dump().phpt | 12 +- tests/PhpGenerator/Dumper.dump().wrap.phpt | 62 +-- tests/PhpGenerator/Dumper.format().phpt | 60 +-- tests/PhpGenerator/Dumper.format().wrap.phpt | 68 ++-- tests/PhpGenerator/Extractor.extractAll.phpt | 8 +- .../Extractor.getFunctionBody.phpt | 30 +- .../Extractor.getMethodBodies.phpt | 58 +-- .../PhpGenerator/InterfaceType.fromCode.phpt | 8 +- tests/PhpGenerator/Method.longParams.phpt | 36 +- tests/PhpGenerator/Method.returnTypes.phpt | 12 +- .../PhpGenerator/Method.scalarParameters.phpt | 12 +- tests/PhpGenerator/Method.validate.phpt | 6 +- tests/PhpGenerator/Method.variadics.phpt | 72 ++-- tests/PhpGenerator/PhpFile.addNamespace.phpt | 17 +- tests/PhpGenerator/PhpNamespace.add.phpt | 21 +- tests/PhpGenerator/PhpNamespace.aliases.phpt | 72 ++-- tests/PhpGenerator/PhpNamespace.phpt | 16 +- tests/PhpGenerator/Printer.phpt | 6 +- tests/PhpGenerator/PsrPrinter.phpt | 4 +- tests/PhpGenerator/invalidNames.phpt | 367 ++++++++++-------- 27 files changed, 649 insertions(+), 503 deletions(-) diff --git a/tests/PhpGenerator/ClassType.addMember.phpt b/tests/PhpGenerator/ClassType.addMember.phpt index f43abb75..c173021e 100644 --- a/tests/PhpGenerator/ClassType.addMember.phpt +++ b/tests/PhpGenerator/ClassType.addMember.phpt @@ -25,6 +25,8 @@ Assert::same('', $method->getBody()); // duplicity $class = new ClassType('Example'); $class->addMember(new Nette\PhpGenerator\Method('foo')); -Assert::exception(function () use ($class) { - $class->addMember(new Nette\PhpGenerator\Method('FOO')); -}, Nette\InvalidStateException::class, "Cannot add member 'FOO', because it already exists."); +Assert::exception( + fn() => $class->addMember(new Nette\PhpGenerator\Method('FOO')), + Nette\InvalidStateException::class, + "Cannot add member 'FOO', because it already exists.", +); diff --git a/tests/PhpGenerator/ClassType.from.bodies.phpt b/tests/PhpGenerator/ClassType.from.bodies.phpt index 7e8258c8..40358a97 100644 --- a/tests/PhpGenerator/ClassType.from.bodies.phpt +++ b/tests/PhpGenerator/ClassType.from.bodies.phpt @@ -15,13 +15,15 @@ Assert::exception(function () { }, Nette\InvalidStateException::class, 'Source code of PDO not found.'); -Assert::exception(function () { - ClassType::from(new class { +Assert::exception( + fn() => ClassType::from(new class { public function f() { } - }, withBodies: true); -}, Nette\NotSupportedException::class, 'The $withBodies parameter cannot be used for anonymous functions.'); + }, withBodies: true), + Nette\NotSupportedException::class, + 'The $withBodies parameter cannot be used for anonymous functions.', +); $res = ClassType::from(Abc\Class7::class, withBodies: true); diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index 2c925281..8fb756e9 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -88,7 +88,7 @@ Assert::true($p->isPublic()); $m = $class->addMethod('getHandle') ->addComment('Returns file handle.') ->addComment('@return resource') - ->setFinal(true) + ->setFinal() ->setBody('return $this->?;', ['handle']); Assert::same($m, $class->getMethod('getHandle')); @@ -102,9 +102,9 @@ Assert::same('public', $m->getVisibility()); Assert::same('return $this->handle;', $m->getBody()); $m = $class->addMethod('getSections') - ->setStatic(true) + ->setStatic() ->setVisibility('protected') - ->setReturnReference(true) + ->setReturnReference() ->addBody('$mode = ?;', [123]) ->addBody('return self::$sections;'); $m->addParameter('mode', new Literal('self::ORDER')); @@ -120,7 +120,7 @@ Assert::true($m->isProtected()); Assert::false($m->isPublic()); $method = $class->addMethod('show') - ->setAbstract(true); + ->setAbstract(); $method->addParameter('foo'); $method->removeParameter('foo'); @@ -128,11 +128,11 @@ $method->removeParameter('foo'); $method->addParameter('item'); $method->addParameter('res', null) - ->setReference(true) + ->setReference() ->setType(Type::union(Type::Array, 'null')); $method->addParameter('bar', null) - ->setNullable(true) + ->setNullable() ->setType('stdClass|string'); $class->addTrait('foo'); @@ -168,37 +168,48 @@ $method->setParameters(array_values($parameters)); Assert::same($parameters, $method->getParameters()); -Assert::exception(function () { - $class = new ClassType; - $class->addMethod('method')->setVisibility('unknown'); -}, Nette\InvalidArgumentException::class, 'Argument must be public|protected|private.'); +Assert::exception( + fn() => (new ClassType)->addMethod('method')->setVisibility('unknown'), + Nette\InvalidArgumentException::class, + 'Argument must be public|protected|private.', +); // duplicity $class = new ClassType('Example'); $class->addConstant('a', 1); -Assert::exception(function () use ($class) { - $class->addConstant('a', 1); -}, Nette\InvalidStateException::class, "Cannot add constant 'a', because it already exists."); +Assert::exception( + fn() => $class->addConstant('a', 1), + Nette\InvalidStateException::class, + "Cannot add constant 'a', because it already exists.", +); $class->addProperty('a'); -Assert::exception(function () use ($class) { - $class->addProperty('a'); -}, Nette\InvalidStateException::class, "Cannot add property 'a', because it already exists."); +Assert::exception( + fn() => $class->addProperty('a'), + Nette\InvalidStateException::class, + "Cannot add property 'a', because it already exists.", +); $class->addMethod('a'); -Assert::exception(function () use ($class) { - $class->addMethod('a'); -}, Nette\InvalidStateException::class, "Cannot add method 'a', because it already exists."); - -Assert::exception(function () use ($class) { - $class->addMethod('A'); -}, Nette\InvalidStateException::class, "Cannot add method 'A', because it already exists."); +Assert::exception( + fn() => $class->addMethod('a'), + Nette\InvalidStateException::class, + "Cannot add method 'a', because it already exists.", +); + +Assert::exception( + fn() => $class->addMethod('A'), + Nette\InvalidStateException::class, + "Cannot add method 'A', because it already exists.", +); $class->addTrait('A'); -Assert::exception(function () use ($class) { - $class->addTrait('A'); -}, Nette\InvalidStateException::class, "Cannot add trait 'A', because it already exists."); +Assert::exception( + fn() => $class->addTrait('A'), + Nette\InvalidStateException::class, + "Cannot add trait 'A', because it already exists.", +); // remove members diff --git a/tests/PhpGenerator/ClassType.validate.phpt b/tests/PhpGenerator/ClassType.validate.phpt index 3ff96b7d..416f282f 100644 --- a/tests/PhpGenerator/ClassType.validate.phpt +++ b/tests/PhpGenerator/ClassType.validate.phpt @@ -11,18 +11,18 @@ require __DIR__ . '/../bootstrap.php'; Assert::exception(function () { $class = new ClassType('A'); - $class->setFinal(true)->setAbstract(true); + $class->setFinal()->setAbstract(); $class->validate(); }, Nette\InvalidStateException::class, "Class 'A' cannot be abstract and final at the same time."); Assert::exception(function () { $class = new ClassType('A'); - $class->setAbstract(true)->setFinal(true); + $class->setAbstract()->setFinal(); $class->validate(); }, Nette\InvalidStateException::class, "Class 'A' cannot be abstract and final at the same time."); Assert::exception(function () { $class = new ClassType; - $class->setAbstract(true); + $class->setAbstract(); $class->validate(); }, Nette\InvalidStateException::class, 'Anonymous class cannot be abstract or final.'); diff --git a/tests/PhpGenerator/Closure.from.phpt b/tests/PhpGenerator/Closure.from.phpt index d1577874..0d0f7662 100644 --- a/tests/PhpGenerator/Closure.from.phpt +++ b/tests/PhpGenerator/Closure.from.phpt @@ -12,7 +12,9 @@ $closure = #[ExampleAttribute] function (stdClass $a, $b = null) {}; $function = Closure::from($closure); same( - '#[ExampleAttribute] function (stdClass $a, $b = null) { -}', + <<<'XX' + #[ExampleAttribute] function (stdClass $a, $b = null) { + } + XX, (string) $function, ); diff --git a/tests/PhpGenerator/Closure.long.phpt b/tests/PhpGenerator/Closure.long.phpt index 516f01da..2ab24ea4 100644 --- a/tests/PhpGenerator/Closure.long.phpt +++ b/tests/PhpGenerator/Closure.long.phpt @@ -17,42 +17,44 @@ for ($name = 'abcde'; $name < 'abcdu'; $name++) { } same( - 'function ( - $abcde, - $abcdf, - $abcdg, - $abcdh, - $abcdi, - $abcdj, - $abcdk, - $abcdl, - $abcdm, - $abcdn, - $abcdo, - $abcdp, - $abcdq, - $abcdr, - $abcds, - $abcdt, -) use ( - $abcde, - $abcdf, - $abcdg, - $abcdh, - $abcdi, - $abcdj, - $abcdk, - $abcdl, - $abcdm, - $abcdn, - $abcdo, - $abcdp, - $abcdq, - $abcdr, - $abcds, - $abcdt, -) { - return null; -}', + <<<'XX' + function ( + $abcde, + $abcdf, + $abcdg, + $abcdh, + $abcdi, + $abcdj, + $abcdk, + $abcdl, + $abcdm, + $abcdn, + $abcdo, + $abcdp, + $abcdq, + $abcdr, + $abcds, + $abcdt, + ) use ( + $abcde, + $abcdf, + $abcdg, + $abcdh, + $abcdi, + $abcdj, + $abcdk, + $abcdl, + $abcdm, + $abcdn, + $abcdo, + $abcdp, + $abcdq, + $abcdr, + $abcds, + $abcdt, + ) { + return null; + } + XX, (string) $function, ); diff --git a/tests/PhpGenerator/Closure.phpt b/tests/PhpGenerator/Closure.phpt index 2702193f..e511c678 100644 --- a/tests/PhpGenerator/Closure.phpt +++ b/tests/PhpGenerator/Closure.phpt @@ -11,19 +11,21 @@ require __DIR__ . '/../bootstrap.php'; $function = new Closure; $function - ->setReturnReference(true) + ->setReturnReference() ->setBody('return $a + $b;'); $function->addParameter('a'); $function->addParameter('b'); $function->addUse('this'); $function->addUse('vars') - ->setReference(true); + ->setReference(); same( - 'function &($a, $b) use ($this, &$vars) { - return $a + $b; -}', + <<<'XX' + function &($a, $b) use ($this, &$vars) { + return $a + $b; + } + XX, (string) $function, ); @@ -36,9 +38,11 @@ Assert::type(Nette\PhpGenerator\Parameter::class, $uses[1]); $uses = $function->setUses([$uses[0]]); same( - 'function &($a, $b) use ($this) { - return $a + $b; -}', + <<<'XX' + function &($a, $b) use ($this) { + return $a + $b; + } + XX, (string) $function, ); @@ -58,9 +62,11 @@ $function ->addUse('this'); same( - 'function () use ($this): array { - return []; -}', + <<<'XX' + function () use ($this): array { + return []; + } + XX, (string) $function, ); @@ -71,8 +77,10 @@ $function->setBody('return $a + $b;'); $function->addAttribute('ExampleAttribute'); same( - '#[ExampleAttribute] function () { - return $a + $b; -}', + <<<'XX' + #[ExampleAttribute] function () { + return $a + $b; + } + XX, (string) $function, ); diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index bb59e060..67e17b5c 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -207,10 +207,12 @@ Assert::same( $dumper->dump(new DateTimeImmutable('2016-06-22 20:52:43.1234', new DateTimeZone('Europe/Prague'))), ); same( - "\\Nette\\PhpGenerator\\Dumper::createObject(\\TestDateTime::class, [ - 'date' => '2016-06-22 20:52:43.123400', - 'timezone_type' => 3, - 'timezone' => 'Europe/Prague', -])", + <<<'XX' + \Nette\PhpGenerator\Dumper::createObject(\TestDateTime::class, [ + 'date' => '2016-06-22 20:52:43.123400', + 'timezone_type' => 3, + 'timezone' => 'Europe/Prague', + ]) + XX, $dumper->dump(new TestDateTime('2016-06-22 20:52:43.1234', new DateTimeZone('Europe/Prague'))), ); diff --git a/tests/PhpGenerator/Dumper.dump().wrap.phpt b/tests/PhpGenerator/Dumper.dump().wrap.phpt index eb429109..01471654 100644 --- a/tests/PhpGenerator/Dumper.dump().wrap.phpt +++ b/tests/PhpGenerator/Dumper.dump().wrap.phpt @@ -16,14 +16,16 @@ require __DIR__ . '/../bootstrap.php'; $dumper = new Dumper; $dumper->wrapLength = 21; same( - "[ - 'a' => [1, 2, 3], - 'aaaaaaaaa' => [ - 1, - 2, - 3, - ], -]", + <<<'XX' + [ + 'a' => [1, 2, 3], + 'aaaaaaaaa' => [ + 1, + 2, + 3, + ], + ] + XX, $dumper->dump([ 'a' => [1, 2, 3], 'aaaaaaaaa' => [1, 2, 3], @@ -31,12 +33,14 @@ same( ); same( - "[ - 'single' => 1 + 2, - 'multi' => [ - 1, - ], -]", + <<<'XX' + [ + 'single' => 1 + 2, + 'multi' => [ + 1, + ], + ] + XX, $dumper->dump([ 'single' => new Literal('1 + 2'), 'multi' => new Literal("[\n\t1,\n]\n"), @@ -44,14 +48,16 @@ same( ); same( - "(object) [ - 'a' => [1, 2, 3], - 'aaaaaaaaa' => [ - 1, - 2, - 3, - ], -]", + <<<'XX' + (object) [ + 'a' => [1, 2, 3], + 'aaaaaaaaa' => [ + 1, + 2, + 3, + ], + ] + XX, $dumper->dump((object) [ 'a' => [1, 2, 3], 'aaaaaaaaa' => [1, 2, 3], @@ -61,9 +67,11 @@ same( $dumper = new Dumper; $dumper->wrapLength = 100; -same("[ +same(<<<'XX' [ - 'a', - 'looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong', - ], -]", $dumper->dump([['a', 'looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong']])); + [ + 'a', + 'looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong', + ], + ] + XX, $dumper->dump([['a', 'looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong']])); diff --git a/tests/PhpGenerator/Dumper.format().phpt b/tests/PhpGenerator/Dumper.format().phpt index 743864de..8400a9e3 100644 --- a/tests/PhpGenerator/Dumper.format().phpt +++ b/tests/PhpGenerator/Dumper.format().phpt @@ -27,35 +27,37 @@ Assert::same('func(1, 2)', $dumper->format('func(?*)', [1, 2])); // old way $dumper->wrapLength = 100; same( - 'func( - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, -)', + <<<'XX' + func( + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + ) + XX, $dumper->format('func(?*)', range(10, 36)), ); diff --git a/tests/PhpGenerator/Dumper.format().wrap.phpt b/tests/PhpGenerator/Dumper.format().wrap.phpt index f5ef45ea..05437587 100644 --- a/tests/PhpGenerator/Dumper.format().wrap.phpt +++ b/tests/PhpGenerator/Dumper.format().wrap.phpt @@ -18,33 +18,53 @@ $dumper->wrapLength = 100; Assert::same('func([1, 2, 3])', $dumper->format('func(?)', [1, 2, 3])); -same('loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong([ - 1, - 2, - 3, -])', $dumper->format('loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong(?)', [1, 2, 3])); - - -same('looooooooooooooooooooooooooooooooooooooooo([1, 2, 3]) + ooooooooooooooooooooooooooooooooooooooooooooooong([ - 1, - 2, - 3, -])', $dumper->format('looooooooooooooooooooooooooooooooooooooooo(?) + ooooooooooooooooooooooooooooooooooooooooooooooong(?)', [1, 2, 3], [1, 2, 3])); +same( + <<<'XX' + loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong([ + 1, + 2, + 3, + ]) + XX, + $dumper->format('loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong(?)', [1, 2, 3]), +); + + +same( + <<<'XX' + looooooooooooooooooooooooooooooooooooooooo([1, 2, 3]) + ooooooooooooooooooooooooooooooooooooooooooooooong([ + 1, + 2, + 3, + ]) + XX, + $dumper->format('looooooooooooooooooooooooooooooooooooooooo(?) + ooooooooooooooooooooooooooooooooooooooooooooooong(?)', [1, 2, 3], [1, 2, 3]), +); // variadics Assert::same('func(1, 2, 3)', $dumper->format('func(...?)', [1, 2, 3])); -same('loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong( - 1, - 2, - 3, -)', $dumper->format('loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong(...?)', [1, 2, 3])); - - -same('looooooooooooooooooooooooooooooooooooooooo(1, 2, 3) + ooooooooooooooooooooooooooooooooooooooooooooooong( - 1, - 2, - 3, -)', $dumper->format('looooooooooooooooooooooooooooooooooooooooo(...?) + ooooooooooooooooooooooooooooooooooooooooooooooong(...?)', [1, 2, 3], [1, 2, 3])); +same( + <<<'XX' + loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong( + 1, + 2, + 3, + ) + XX, + $dumper->format('loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong(...?)', [1, 2, 3]), +); + + +same( + <<<'XX' + looooooooooooooooooooooooooooooooooooooooo(1, 2, 3) + ooooooooooooooooooooooooooooooooooooooooooooooong( + 1, + 2, + 3, + ) + XX, + $dumper->format('looooooooooooooooooooooooooooooooooooooooo(...?) + ooooooooooooooooooooooooooooooooooooooooooooooong(...?)', [1, 2, 3], [1, 2, 3]), +); diff --git a/tests/PhpGenerator/Extractor.extractAll.phpt b/tests/PhpGenerator/Extractor.extractAll.phpt index e57556c1..a04edc8d 100644 --- a/tests/PhpGenerator/Extractor.extractAll.phpt +++ b/tests/PhpGenerator/Extractor.extractAll.phpt @@ -30,6 +30,8 @@ sameFile(__DIR__ . '/expected/Extractor.bodies.expect', (string) $file); $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/extractor.php')))->extractAll(); sameFile(__DIR__ . '/expected/Extractor.expect', (string) $file); -Assert::exception(function () { - (new Extractor('')); -}, Nette\InvalidStateException::class, 'The input string is not a PHP code.'); +Assert::exception( + fn() => new Extractor(''), + Nette\InvalidStateException::class, + 'The input string is not a PHP code.', +); diff --git a/tests/PhpGenerator/Extractor.getFunctionBody.phpt b/tests/PhpGenerator/Extractor.getFunctionBody.phpt index 70321187..aef8a994 100644 --- a/tests/PhpGenerator/Extractor.getFunctionBody.phpt +++ b/tests/PhpGenerator/Extractor.getFunctionBody.phpt @@ -9,20 +9,22 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$extractor = new Extractor('name; + case Red; + case Blue; + + public function getName(): string + { + return $this->name; + } } -} -'); + + XX); $bodies = $extractor->extractMethodBodies('NS\Undefined'); Assert::same([], $bodies); diff --git a/tests/PhpGenerator/InterfaceType.fromCode.phpt b/tests/PhpGenerator/InterfaceType.fromCode.phpt index d12262da..34272daa 100644 --- a/tests/PhpGenerator/InterfaceType.fromCode.phpt +++ b/tests/PhpGenerator/InterfaceType.fromCode.phpt @@ -22,6 +22,8 @@ Assert::match(<<<'XX' XX, (string) $class); -Assert::exception(function () { - InterfaceType::fromCode(' InterfaceType::fromCode('setBody('return new Foo();'); same( - 'function create(): Foo -{ - return new Foo(); -} -', + <<<'XX' + function create(): Foo + { + return new Foo(); + } + + XX, (string) $method, ); diff --git a/tests/PhpGenerator/Method.scalarParameters.phpt b/tests/PhpGenerator/Method.scalarParameters.phpt index 2cb36a86..a121d71d 100644 --- a/tests/PhpGenerator/Method.scalarParameters.phpt +++ b/tests/PhpGenerator/Method.scalarParameters.phpt @@ -43,10 +43,12 @@ $method->addParameter('a')->setType(Type::String); $method->addParameter('b')->setType(Type::Bool); same( - 'function create(string $a, bool $b) -{ - return null; -} -', + <<<'XX' + function create(string $a, bool $b) + { + return null; + } + + XX, (string) $method, ); diff --git a/tests/PhpGenerator/Method.validate.phpt b/tests/PhpGenerator/Method.validate.phpt index ce9ca61e..46112656 100644 --- a/tests/PhpGenerator/Method.validate.phpt +++ b/tests/PhpGenerator/Method.validate.phpt @@ -11,18 +11,18 @@ require __DIR__ . '/../bootstrap.php'; Assert::exception(function () { $method = new Method('foo'); - $method->setFinal(true)->setAbstract(true); + $method->setFinal()->setAbstract(); $method->validate(); }, Nette\InvalidStateException::class, 'Method foo() cannot be abstract and final or private at the same time.'); Assert::exception(function () { $method = new Method('foo'); - $method->setAbstract(true)->setFinal(true); + $method->setAbstract()->setFinal(); $method->validate(); }, Nette\InvalidStateException::class, 'Method foo() cannot be abstract and final or private at the same time.'); Assert::exception(function () { $method = new Method('foo'); - $method->setAbstract(true)->setVisibility('private'); + $method->setAbstract()->setVisibility('private'); $method->validate(); }, Nette\InvalidStateException::class, 'Method foo() cannot be abstract and final or private at the same time.'); diff --git a/tests/PhpGenerator/Method.variadics.phpt b/tests/PhpGenerator/Method.variadics.phpt index eace3529..beea8f02 100644 --- a/tests/PhpGenerator/Method.variadics.phpt +++ b/tests/PhpGenerator/Method.variadics.phpt @@ -36,80 +36,90 @@ Assert::same('array', $method->getParameters()['bar']->getType()); // parameterless variadic method $method = (new Method('variadic')) - ->setVariadic(true) + ->setVariadic() ->setBody('return 42;'); same( - 'function variadic() -{ - return 42; -} -', + <<<'XX' + function variadic() + { + return 42; + } + + XX, (string) $method, ); // variadic method with one parameter $method = (new Method('variadic')) - ->setVariadic(true) + ->setVariadic() ->setBody('return 42;'); $method->addParameter('foo'); same( - 'function variadic(...$foo) -{ - return 42; -} -', + <<<'XX' + function variadic(...$foo) + { + return 42; + } + + XX, (string) $method, ); // variadic method with multiple parameters $method = (new Method('variadic')) - ->setVariadic(true) + ->setVariadic() ->setBody('return 42;'); $method->addParameter('foo'); $method->addParameter('bar'); $method->addParameter('baz', []); same( - 'function variadic($foo, $bar, ...$baz) -{ - return 42; -} -', + <<<'XX' + function variadic($foo, $bar, ...$baz) + { + return 42; + } + + XX, (string) $method, ); // method with typehinted variadic param $method = (new Method('variadic')) - ->setVariadic(true) + ->setVariadic() ->setBody('return 42;'); $method->addParameter('foo')->setType('array'); same( - 'function variadic(array ...$foo) -{ - return 42; -} -', + <<<'XX' + function variadic(array ...$foo) + { + return 42; + } + + XX, (string) $method, ); // method with typrhinted by-value variadic param $method = (new Method('variadic')) - ->setVariadic(true) + ->setVariadic() ->setBody('return 42;'); -$method->addParameter('foo')->setType('array')->setReference(true); +$method->addParameter('foo')->setType('array')->setReference(); same( - 'function variadic(array &...$foo) -{ - return 42; -} -', + <<<'XX' + function variadic(array &...$foo) + { + return 42; + } + + XX, (string) $method, ); diff --git a/tests/PhpGenerator/PhpFile.addNamespace.phpt b/tests/PhpGenerator/PhpFile.addNamespace.phpt index 16d46663..d8aa4946 100644 --- a/tests/PhpGenerator/PhpFile.addNamespace.phpt +++ b/tests/PhpGenerator/PhpFile.addNamespace.phpt @@ -15,11 +15,16 @@ $phpFile->addNamespace('Foo'); $phpFile->addNamespace($namespace); // overwrite -same('add($classB = new ClassType('B', new PhpNamespace('X'))); -same('namespace Foo; +same( + <<<'XX' + namespace Foo; -class A -{ -} + class A + { + } -class B -{ -} -', (string) $namespace); + class B + { + } + + XX, + (string) $namespace, +); // namespaces are not changed Assert::null($classA->getNamespace()); diff --git a/tests/PhpGenerator/PhpNamespace.aliases.phpt b/tests/PhpGenerator/PhpNamespace.aliases.phpt index 5f8dc034..fbe5b665 100644 --- a/tests/PhpGenerator/PhpNamespace.aliases.phpt +++ b/tests/PhpGenerator/PhpNamespace.aliases.phpt @@ -12,40 +12,56 @@ require __DIR__ . '/../bootstrap.php'; $namespace = new PhpNamespace('Foo'); $namespace->addUse('Bar\C'); -Assert::exception(function () use ($namespace) { - $namespace->addTrait('C'); -}, Nette\InvalidStateException::class, "Name 'C' used already as alias for Bar\\C."); - -Assert::exception(function () use ($namespace) { - $namespace->addTrait('c'); -}, Nette\InvalidStateException::class, "Name 'c' used already as alias for Bar\\C."); +Assert::exception( + fn() => $namespace->addTrait('C'), + Nette\InvalidStateException::class, + "Name 'C' used already as alias for Bar\\C.", +); + +Assert::exception( + fn() => $namespace->addTrait('c'), + Nette\InvalidStateException::class, + "Name 'c' used already as alias for Bar\\C.", +); $namespace->addClass('B'); -Assert::exception(function () use ($namespace) { - $namespace->addUse('Lorem\B', 'B'); -}, Nette\InvalidStateException::class, "Name 'B' used already for 'Foo\\B'."); - -Assert::exception(function () use ($namespace) { - $namespace->addUse('lorem\b', 'b'); -}, Nette\InvalidStateException::class, "Name 'b' used already for 'Foo\\B'."); +Assert::exception( + fn() => $namespace->addUse('Lorem\B', 'B'), + Nette\InvalidStateException::class, + "Name 'B' used already for 'Foo\\B'.", +); + +Assert::exception( + fn() => $namespace->addUse('lorem\b', 'b'), + Nette\InvalidStateException::class, + "Name 'b' used already for 'Foo\\B'.", +); $namespace->addUseFunction('Bar\f1'); -Assert::exception(function () use ($namespace) { - $namespace->addFunction('f1'); -}, Nette\InvalidStateException::class, "Name 'f1' used already as alias for Bar\\f1."); - -Assert::exception(function () use ($namespace) { - $namespace->addFunction('F1'); -}, Nette\InvalidStateException::class, "Name 'F1' used already as alias for Bar\\f1."); +Assert::exception( + fn() => $namespace->addFunction('f1'), + Nette\InvalidStateException::class, + "Name 'f1' used already as alias for Bar\\f1.", +); + +Assert::exception( + fn() => $namespace->addFunction('F1'), + Nette\InvalidStateException::class, + "Name 'F1' used already as alias for Bar\\f1.", +); $namespace->addFunction('f2'); -Assert::exception(function () use ($namespace) { - $namespace->addUseFunction('Bar\f2', 'f2'); -}, Nette\InvalidStateException::class, "Name 'f2' used already for 'Foo\\f2'."); - -Assert::exception(function () use ($namespace) { - $namespace->addUseFunction('Bar\f2', 'F2'); -}, Nette\InvalidStateException::class, "Name 'F2' used already for 'Foo\\f2'."); +Assert::exception( + fn() => $namespace->addUseFunction('Bar\f2', 'f2'), + Nette\InvalidStateException::class, + "Name 'f2' used already for 'Foo\\f2'.", +); + +Assert::exception( + fn() => $namespace->addUseFunction('Bar\f2', 'F2'), + Nette\InvalidStateException::class, + "Name 'F2' used already for 'Foo\\f2'.", +); Assert::same(['C' => 'Bar\C'], $namespace->getUses()); Assert::same(['f1' => 'Bar\f1'], $namespace->getUses($namespace::NameFunction)); diff --git a/tests/PhpGenerator/PhpNamespace.phpt b/tests/PhpGenerator/PhpNamespace.phpt index 5f27a9fb..1542ad04 100644 --- a/tests/PhpGenerator/PhpNamespace.phpt +++ b/tests/PhpGenerator/PhpNamespace.phpt @@ -17,9 +17,11 @@ Assert::same('Foo', $namespace->getName()); $classA = $namespace->addClass('A'); Assert::same($namespace, $classA->getNamespace()); -Assert::exception(function () use ($namespace) { - $namespace->addClass('a'); -}, Nette\InvalidStateException::class, "Cannot add 'a', because it already exists."); +Assert::exception( + fn() => $namespace->addClass('a'), + Nette\InvalidStateException::class, + "Cannot add 'a', because it already exists.", +); $interfaceB = $namespace->addInterface('B'); Assert::same($namespace, $interfaceB->getNamespace()); @@ -32,9 +34,11 @@ Assert::count(1, $namespace->getClasses()); $function = $namespace->addFunction('foo'); -Assert::exception(function () use ($namespace) { - $namespace->addFunction('Foo'); -}, Nette\InvalidStateException::class, "Cannot add 'Foo', because it already exists."); +Assert::exception( + fn() => $namespace->addFunction('Foo'), + Nette\InvalidStateException::class, + "Cannot add 'Foo', because it already exists.", +); Assert::count(1, $namespace->getFunctions()); Assert::same($function, $namespace->getFunctions()['foo']); diff --git a/tests/PhpGenerator/Printer.phpt b/tests/PhpGenerator/Printer.phpt index efecd0b5..97d8284e 100644 --- a/tests/PhpGenerator/Printer.phpt +++ b/tests/PhpGenerator/Printer.phpt @@ -15,7 +15,7 @@ $printer = new Printer; $class = (new ClassType('Example')) - ->setFinal(true) + ->setFinal() ->setExtends('ParentClass') ->addImplement('IExample') ->addComment("Description of class.\nThis is example\n"); @@ -43,7 +43,7 @@ $class->addProperty('short', ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, $class->addMethod('first') ->addComment('@return resource') - ->setFinal(true) + ->setFinal() ->setReturnType('stdClass') ->setBody("func(); \r\nreturn ?;", [['aaaaaaaaaaaa' => 1, 'bbbbbbbbbbb' => 2, 'cccccccccccccc' => 3, 'dddddddddddd' => 4, 'eeeeeeeeeeee' => 5, 'ffffffff' => 6]]) ->addParameter('var') @@ -85,6 +85,6 @@ sameFile(__DIR__ . '/expected/Printer.closure.expect', $printer->printClosure($c // printer validates class Assert::exception(function () { $class = new ClassType; - $class->setFinal(true)->setAbstract(true); + $class->setFinal()->setAbstract(); (new Printer)->printClass($class); }, Nette\InvalidStateException::class, 'Anonymous class cannot be abstract or final.'); diff --git a/tests/PhpGenerator/PsrPrinter.phpt b/tests/PhpGenerator/PsrPrinter.phpt index 05ebc849..eb498405 100644 --- a/tests/PhpGenerator/PsrPrinter.phpt +++ b/tests/PhpGenerator/PsrPrinter.phpt @@ -14,7 +14,7 @@ $printer = new PsrPrinter; $class = (new ClassType('Example')) - ->setFinal(true) + ->setFinal() ->setExtends('ParentClass') ->addImplement('IExample') ->addComment("Description of class.\nThis is example\n"); @@ -42,7 +42,7 @@ $class->addProperty('short', ['aaaaaaaa' => 1, 'bbbbbbbb' => 2, 'cccccccc' => 3, $class->addMethod('first') ->addComment('@return resource') - ->setFinal(true) + ->setFinal() ->setReturnType('stdClass') ->setBody("func();\nreturn ?;", [['aaaaaaaaaaaa' => 1, 'bbbbbbbbbbb' => 2, 'cccccccccccccc' => 3, 'dddddddddddd' => 4, 'eeeeeeeeeeee' => 5, 'ffffffff' => 6]]) ->addParameter('var') diff --git a/tests/PhpGenerator/invalidNames.phpt b/tests/PhpGenerator/invalidNames.phpt index 57b8ff21..3a70e250 100644 --- a/tests/PhpGenerator/invalidNames.phpt +++ b/tests/PhpGenerator/invalidNames.phpt @@ -11,206 +11,239 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -Assert::noError(function () { - new Nette\PhpGenerator\PhpNamespace(''); // global namespace - new Nette\PhpGenerator\PhpNamespace('Iñtërnâti\ônàlizætiøn'); -}); - -Assert::exception(function () { - new Nette\PhpGenerator\PhpNamespace(null); -}, TypeError::class); - -Assert::exception(function () { - new Nette\PhpGenerator\PhpNamespace('*'); -}, Nette\InvalidArgumentException::class); - -Assert::exception(function () { - new Nette\PhpGenerator\PhpNamespace('abc abc'); -}, Nette\InvalidArgumentException::class); - -Assert::exception(function () { - new Nette\PhpGenerator\PhpNamespace('abc\\'); -}, Nette\InvalidArgumentException::class); - -Assert::exception(function () { - new Nette\PhpGenerator\PhpNamespace('\abc'); -}, Nette\InvalidArgumentException::class); - -Assert::exception(function () { - (new Nette\PhpGenerator\PhpNamespace('Abc'))->addUse(''); -}, Nette\InvalidArgumentException::class); - -Assert::exception(function () { - (new Nette\PhpGenerator\PhpNamespace('Abc'))->addUse('Foo', 'a b'); -}, Nette\InvalidArgumentException::class); - -Assert::exception(function () { - (new Nette\PhpGenerator\PhpNamespace('Abc'))->addUse('true'); -}, Nette\InvalidArgumentException::class); - -Assert::exception(function () { - (new Nette\PhpGenerator\PhpNamespace('Abc'))->addUse('aaa', 'true'); -}, Nette\InvalidArgumentException::class); - - -Assert::noError(function () { - new Nette\PhpGenerator\ClassType(null); // anonymous class - new Nette\PhpGenerator\ClassType('Iñtërnâtiônàlizætiøn'); -}); - -Assert::exception(function () { - new Nette\PhpGenerator\ClassType(''); -}, Nette\InvalidArgumentException::class); - -Assert::exception(function () { - new Nette\PhpGenerator\ClassType('*'); -}, Nette\InvalidArgumentException::class); - -Assert::exception(function () { - new Nette\PhpGenerator\ClassType('abc abc'); -}, Nette\InvalidArgumentException::class); - -Assert::exception(function () { - new Nette\PhpGenerator\ClassType('abc\abc'); -}, Nette\InvalidArgumentException::class); - -Assert::exception(function () { - new Nette\PhpGenerator\ClassType('\abc'); -}, Nette\InvalidArgumentException::class); - -Assert::exception(function () { - new Nette\PhpGenerator\ClassType('enum'); -}, Nette\InvalidArgumentException::class); - -Assert::exception(function () { - new Nette\PhpGenerator\ClassType('bool'); -}, Nette\InvalidArgumentException::class); +Assert::noError(fn() => new Nette\PhpGenerator\PhpNamespace('')); +Assert::noError(fn() => new Nette\PhpGenerator\PhpNamespace('Iñtërnâti\ônàlizætiøn')); + +Assert::exception( + fn() => new Nette\PhpGenerator\PhpNamespace(null), + TypeError::class, +); + +Assert::exception( + fn() => new Nette\PhpGenerator\PhpNamespace('*'), + Nette\InvalidArgumentException::class, +); + +Assert::exception( + fn() => new Nette\PhpGenerator\PhpNamespace('abc abc'), + Nette\InvalidArgumentException::class, +); + +Assert::exception( + fn() => new Nette\PhpGenerator\PhpNamespace('abc\\'), + Nette\InvalidArgumentException::class, +); + +Assert::exception( + fn() => new Nette\PhpGenerator\PhpNamespace('\abc'), + Nette\InvalidArgumentException::class, +); + +Assert::exception( + fn() => (new Nette\PhpGenerator\PhpNamespace('Abc'))->addUse(''), + Nette\InvalidArgumentException::class, +); + +Assert::exception( + fn() => (new Nette\PhpGenerator\PhpNamespace('Abc'))->addUse('Foo', 'a b'), + Nette\InvalidArgumentException::class, +); + +Assert::exception( + fn() => (new Nette\PhpGenerator\PhpNamespace('Abc'))->addUse('true'), + Nette\InvalidArgumentException::class, +); + +Assert::exception( + fn() => (new Nette\PhpGenerator\PhpNamespace('Abc'))->addUse('aaa', 'true'), + Nette\InvalidArgumentException::class, +); + + +Assert::noError(fn() => new Nette\PhpGenerator\ClassType(null)); +Assert::noError(fn() => new Nette\PhpGenerator\ClassType('Iñtërnâtiônàlizætiøn')); + +Assert::exception( + fn() => new Nette\PhpGenerator\ClassType(''), + Nette\InvalidArgumentException::class, +); + +Assert::exception( + fn() => new Nette\PhpGenerator\ClassType('*'), + Nette\InvalidArgumentException::class, +); + +Assert::exception( + fn() => new Nette\PhpGenerator\ClassType('abc abc'), + Nette\InvalidArgumentException::class, +); + +Assert::exception( + fn() => new Nette\PhpGenerator\ClassType('abc\abc'), + Nette\InvalidArgumentException::class, +); + +Assert::exception( + fn() => new Nette\PhpGenerator\ClassType('\abc'), + Nette\InvalidArgumentException::class, +); + +Assert::exception( + fn() => new Nette\PhpGenerator\ClassType('enum'), + Nette\InvalidArgumentException::class, +); + +Assert::exception( + fn() => new Nette\PhpGenerator\ClassType('bool'), + Nette\InvalidArgumentException::class, +); $class = new Nette\PhpGenerator\ClassType('Abc'); -Assert::exception(function () use ($class) { - $class->setExtends('*'); -}, Nette\InvalidArgumentException::class, "Value '*' is not valid class name."); - -Assert::exception(function () use ($class) { - $class->setImplements(['A', '*']); -}, Nette\InvalidArgumentException::class, "Value '*' is not valid class name."); - -Assert::exception(function () use ($class) { - $class->addImplement('*'); -}, Nette\InvalidArgumentException::class, "Value '*' is not valid class name."); - -Assert::exception(function () use ($class) { - $class->addTrait('*'); -}, Nette\InvalidArgumentException::class, "Value '*' is not valid trait name."); +Assert::exception( + fn() => $class->setExtends('*'), + Nette\InvalidArgumentException::class, + "Value '*' is not valid class name.", +); + +Assert::exception( + fn() => $class->setImplements(['A', '*']), + Nette\InvalidArgumentException::class, + "Value '*' is not valid class name.", +); + +Assert::exception( + fn() => $class->addImplement('*'), + Nette\InvalidArgumentException::class, + "Value '*' is not valid class name.", +); + +Assert::exception( + fn() => $class->addTrait('*'), + Nette\InvalidArgumentException::class, + "Value '*' is not valid trait name.", +); $iface = new Nette\PhpGenerator\InterfaceType('Abc'); -Assert::exception(function () use ($iface) { - $iface->setExtends(['A', '*']); -}, Nette\InvalidArgumentException::class, "Value '*' is not valid class name."); +Assert::exception( + fn() => $iface->setExtends(['A', '*']), + Nette\InvalidArgumentException::class, + "Value '*' is not valid class name.", +); -Assert::exception(function () use ($iface) { - $iface->addExtend('*'); -}, Nette\InvalidArgumentException::class, "Value '*' is not valid class name."); +Assert::exception( + fn() => $iface->addExtend('*'), + Nette\InvalidArgumentException::class, + "Value '*' is not valid class name.", +); -Assert::noError(function () { - new Nette\PhpGenerator\Property('Iñtërnâtiônàlizætiøn'); -}); +Assert::noError(fn() => new Nette\PhpGenerator\Property('Iñtërnâtiônàlizætiøn')); -Assert::exception(function () { - new Nette\PhpGenerator\Property(null); -}, TypeError::class); +Assert::exception( + fn() => new Nette\PhpGenerator\Property(null), + TypeError::class, +); -Assert::exception(function () { - new Nette\PhpGenerator\Property(''); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => new Nette\PhpGenerator\Property(''), + Nette\InvalidArgumentException::class, +); -Assert::exception(function () { - new Nette\PhpGenerator\Property('*'); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => new Nette\PhpGenerator\Property('*'), + Nette\InvalidArgumentException::class, +); -Assert::exception(function () { - (new Nette\PhpGenerator\Property('foo'))->setType('a b'); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => (new Nette\PhpGenerator\Property('foo'))->setType('a b'), + Nette\InvalidArgumentException::class, +); -Assert::noError(function () { - new Nette\PhpGenerator\Parameter('Iñtërnâtiônàlizætiøn'); -}); +Assert::noError(fn() => new Nette\PhpGenerator\Parameter('Iñtërnâtiônàlizætiøn')); -Assert::exception(function () { - new Nette\PhpGenerator\Parameter(''); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => new Nette\PhpGenerator\Parameter(''), + Nette\InvalidArgumentException::class, +); -Assert::exception(function () { - new Nette\PhpGenerator\Parameter(null); -}, TypeError::class); +Assert::exception( + fn() => new Nette\PhpGenerator\Parameter(null), + TypeError::class, +); -Assert::exception(function () { - new Nette\PhpGenerator\Parameter('*'); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => new Nette\PhpGenerator\Parameter('*'), + Nette\InvalidArgumentException::class, +); -Assert::exception(function () { - new Nette\PhpGenerator\Parameter('$test'); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => new Nette\PhpGenerator\Parameter('$test'), + Nette\InvalidArgumentException::class, +); -Assert::exception(function () { - (new Nette\PhpGenerator\Parameter('foo'))->setType('a b'); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => (new Nette\PhpGenerator\Parameter('foo'))->setType('a b'), + Nette\InvalidArgumentException::class, +); -Assert::noError(function () { - new Nette\PhpGenerator\Method('Iñtërnâtiônàlizætiøn'); -}); +Assert::noError(fn() => new Nette\PhpGenerator\Method('Iñtërnâtiônàlizætiøn')); -Assert::exception(function () { - new Nette\PhpGenerator\Method(''); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => new Nette\PhpGenerator\Method(''), + Nette\InvalidArgumentException::class, +); -Assert::exception(function () { - new Nette\PhpGenerator\Method(null); -}, TypeError::class); +Assert::exception( + fn() => new Nette\PhpGenerator\Method(null), + TypeError::class, +); -Assert::exception(function () { - new Nette\PhpGenerator\Method('*'); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => new Nette\PhpGenerator\Method('*'), + Nette\InvalidArgumentException::class, +); -Assert::exception(function () { - (new Nette\PhpGenerator\Method('foo'))->setReturnType('a b'); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => (new Nette\PhpGenerator\Method('foo'))->setReturnType('a b'), + Nette\InvalidArgumentException::class, +); -Assert::noError(function () { - new Nette\PhpGenerator\GlobalFunction('Iñtërnâtiônàlizætiøn'); -}); +Assert::noError(fn() => new Nette\PhpGenerator\GlobalFunction('Iñtërnâtiônàlizætiøn')); -Assert::exception(function () { - new Nette\PhpGenerator\GlobalFunction(''); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => new Nette\PhpGenerator\GlobalFunction(''), + Nette\InvalidArgumentException::class, +); -Assert::exception(function () { - new Nette\PhpGenerator\GlobalFunction(null); -}, TypeError::class); +Assert::exception( + fn() => new Nette\PhpGenerator\GlobalFunction(null), + TypeError::class, +); -Assert::exception(function () { - new Nette\PhpGenerator\GlobalFunction('*'); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => new Nette\PhpGenerator\GlobalFunction('*'), + Nette\InvalidArgumentException::class, +); -Assert::noError(function () { - new Nette\PhpGenerator\Constant('Iñtërnâtiônàlizætiøn'); -}); +Assert::noError(fn() => new Nette\PhpGenerator\Constant('Iñtërnâtiônàlizætiøn')); -Assert::exception(function () { - new Nette\PhpGenerator\Constant(null); -}, TypeError::class); +Assert::exception( + fn() => new Nette\PhpGenerator\Constant(null), + TypeError::class, +); -Assert::exception(function () { - new Nette\PhpGenerator\Constant(''); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => new Nette\PhpGenerator\Constant(''), + Nette\InvalidArgumentException::class, +); -Assert::exception(function () { - new Nette\PhpGenerator\Constant('*'); -}, Nette\InvalidArgumentException::class); +Assert::exception( + fn() => new Nette\PhpGenerator\Constant('*'), + Nette\InvalidArgumentException::class, +); From 8853465f5488a9003fab9c9a73933f2600b33adf Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 26 Apr 2023 12:55:09 +0200 Subject: [PATCH 161/266] Printer: attributes on parameters are placed on its own line --- src/PhpGenerator/Printer.php | 52 +++++--- tests/PhpGenerator/Printer.function.phpt | 121 +++++++++++++++++- .../expected/ClassType.attributes.expect | 3 +- .../expected/ClassType.from.expect | 3 +- .../expected/ClassType.promotion.expect | 3 +- .../expected/Extractor.classes.expect | 3 +- .../PhpGenerator/expected/PhpNamespace.expect | 3 +- .../expected/Printer.class-alt.expect | 6 +- .../expected/Printer.class.expect | 6 +- 9 files changed, 166 insertions(+), 34 deletions(-) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 4731fbe3..1b4fea73 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -326,37 +326,51 @@ protected function printUses(PhpNamespace $namespace, string $of = PhpNamespace: protected function printParameters(Closure|GlobalFunction|Method $function, int $column = 0): string { - $params = []; - $list = $function->getParameters(); - $multiline = false; - - foreach ($list as $param) { + $special = false; + foreach ($function->getParameters() as $param) { $param->validate(); - $variadic = $function->isVariadic() && $param === end($list); - $type = $param->getType(); + $special = $special || $param instanceof PromotedParameter || $param->getAttributes(); + } + + if (!$special) { + $line = $this->formatParameters($function, false); + if (!str_contains($line, "\n") && strlen($line) + $column <= $this->wrapLength) { + return $line; + } + } + + return $this->formatParameters($function, true); + } + + + private function formatParameters(Closure|GlobalFunction|Method $function, bool $multiline): string + { + $params = $function->getParameters(); + $res = ''; + + foreach ($params as $param) { + $variadic = $function->isVariadic() && $param === end($params); $promoted = $param instanceof PromotedParameter ? $param : null; - $params[] = + $attrs = $this->printAttributes($param->getAttributes(), inline: true); + $res .= ($promoted ? $this->printDocComment($promoted) : '') - . ($attrs = $this->printAttributes($param->getAttributes(), inline: true)) + . ($attrs ? ($multiline ? substr($attrs, 0, -1) . "\n" : $attrs) : '') . ($promoted ? ($promoted->getVisibility() ?: 'public') - . ($promoted->isReadOnly() && $type ? ' readonly' : '') + . ($promoted->isReadOnly() && $param->getType() ? ' readonly' : '') . ' ' : '') - . ltrim($this->printType($type, $param->isNullable()) . ' ') + . ltrim($this->printType($param->getType(), $param->isNullable()) . ' ') . ($param->isReference() ? '&' : '') . ($variadic ? '...' : '') . '$' . $param->getName() - . ($param->hasDefaultValue() && !$variadic ? ' = ' . $this->dump($param->getDefaultValue()) : ''); - - $multiline = $multiline || $promoted || $attrs; + . ($param->hasDefaultValue() && !$variadic ? ' = ' . $this->dump($param->getDefaultValue()) : '') + . ($multiline ? ",\n" : ', '); } - $line = implode(', ', $params); - $multiline = $multiline || count($params) > 1 && (strlen($line) + $column > $this->wrapLength); - return $multiline - ? "(\n" . $this->indent(implode(",\n", $params)) . ",\n)" - : "($line)"; + ? "(\n" . $this->indent($res) . ')' + : '(' . substr($res, 0, -2) . ')'; + } diff --git a/tests/PhpGenerator/Printer.function.phpt b/tests/PhpGenerator/Printer.function.phpt index f9ed7719..c75a6545 100644 --- a/tests/PhpGenerator/Printer.function.phpt +++ b/tests/PhpGenerator/Printer.function.phpt @@ -2,6 +2,7 @@ declare(strict_types=1); +use Nette\PhpGenerator\GlobalFunction; use Nette\PhpGenerator\Printer; use Tester\Assert; @@ -9,7 +10,7 @@ require __DIR__ . '/../bootstrap.php'; $printer = new Printer; -$function = new Nette\PhpGenerator\GlobalFunction('func'); +$function = new GlobalFunction('func'); $function ->setReturnType('stdClass') ->setBody("func(); \r\nreturn 123;") @@ -26,20 +27,20 @@ Assert::match(<<<'XX' XX, $printer->printFunction($function)); -$function = new Nette\PhpGenerator\GlobalFunction('multi'); +$function = new GlobalFunction('multi'); $function->addParameter('foo') ->addAttribute('Foo'); Assert::match(<<<'XX' function multi( - #[Foo] $foo, + #[Foo] + $foo, ) { } - XX, $printer->printFunction($function)); -$function = new Nette\PhpGenerator\GlobalFunction('multiType'); +$function = new GlobalFunction('multiType'); $function ->setReturnType('array') ->addParameter('foo') @@ -47,9 +48,117 @@ $function Assert::match(<<<'XX' function multiType( - #[Foo] $foo, + #[Foo] + $foo, ): array { } + XX, $printer->printFunction($function)); + + +$function = new GlobalFunction('func'); +$function->addAttribute('Foo', [1, 2, 3]); +$function->addAttribute('Bar'); + +same( + <<<'XX' + #[Foo(1, 2, 3)] + #[Bar] + function func() + { + } + + XX, + (string) $function, +); + + +// single +$function = new GlobalFunction('func'); +$function->addAttribute('Foo', [1, 2, 3]); + +Assert::match(<<<'XX' + #[Foo(1, 2, 3)] + function func() + { + } + XX, $printer->printFunction($function)); + + +// multiple +$function = new GlobalFunction('func'); +$function->addAttribute('Foo', [1, 2, 3]); +$function->addAttribute('Bar'); + +Assert::match(<<<'XX' + #[Foo(1, 2, 3)] + #[Bar] + function func() + { + } + XX, $printer->printFunction($function)); + + +// multiline +$function = new GlobalFunction('func'); +$function->addAttribute('Foo', ['a', str_repeat('x', 120)]); + +Assert::match(<<<'XX' + #[Foo( + 'a', + 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', + )] + function func() + { + } + XX, $printer->printFunction($function)); + + +// parameter: single +$function = new GlobalFunction('func'); +$param = $function->addParameter('foo'); +$param->addAttribute('Foo', [1, 2, 3]); + +Assert::match(<<<'XX' + function func( + #[Foo(1, 2, 3)] + $foo, + ) { + } + XX, $printer->printFunction($function)); + +// parameter: multiple +$function = new GlobalFunction('func'); +$param = $function->addParameter('foo'); +$param->addAttribute('Foo', [1, 2, 3]); +$param->addAttribute('Bar'); + +Assert::match(<<<'XX' + function func( + #[Foo(1, 2, 3), Bar] + $foo, + ) { + } + XX, $printer->printFunction($function)); + + +// parameter: multiline +$function = new GlobalFunction('func'); +$param = $function->addParameter('bar'); +$param->addAttribute('Foo'); +$param = $function->addParameter('foo'); +$param->addAttribute('Foo', ['a', str_repeat('x', 120)]); + +Assert::match(<<<'XX' + function func( + #[Foo] + $bar, + #[Foo( + 'a', + 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', + )] + $foo, + ) { + } XX, $printer->printFunction($function)); diff --git a/tests/PhpGenerator/expected/ClassType.attributes.expect b/tests/PhpGenerator/expected/ClassType.attributes.expect index 57d6307a..9ed82714 100644 --- a/tests/PhpGenerator/expected/ClassType.attributes.expect +++ b/tests/PhpGenerator/expected/ClassType.attributes.expect @@ -21,7 +21,8 @@ class Example */ #[ExampleAttribute] public function getHandle( - #[ExampleAttribute, WithArguments(123)] $mode, + #[ExampleAttribute, WithArguments(123)] + $mode, ) { } } diff --git a/tests/PhpGenerator/expected/ClassType.from.expect b/tests/PhpGenerator/expected/ClassType.from.expect index f66a53a9..a6f42d2a 100644 --- a/tests/PhpGenerator/expected/ClassType.from.expect +++ b/tests/PhpGenerator/expected/ClassType.from.expect @@ -139,7 +139,8 @@ class Class9 */ #[ExampleAttribute] public function getHandle( - #[WithArguments(123)] $mode, + #[WithArguments(123)] + $mode, ) { } } diff --git a/tests/PhpGenerator/expected/ClassType.promotion.expect b/tests/PhpGenerator/expected/ClassType.promotion.expect index 81dd351e..78700ad6 100644 --- a/tests/PhpGenerator/expected/ClassType.promotion.expect +++ b/tests/PhpGenerator/expected/ClassType.promotion.expect @@ -4,7 +4,8 @@ class Example $a, public $b, /** promo */ - #[Example] private string $c, + #[Example] + private string $c, public readonly Draft $d = new Draft(10), ) { } diff --git a/tests/PhpGenerator/expected/Extractor.classes.expect b/tests/PhpGenerator/expected/Extractor.classes.expect index 25d94e84..3397988c 100644 --- a/tests/PhpGenerator/expected/Extractor.classes.expect +++ b/tests/PhpGenerator/expected/Extractor.classes.expect @@ -149,7 +149,8 @@ class Class9 */ #[ExampleAttribute] public function getHandle( - #[WithArguments(123)] $mode, + #[WithArguments(123)] + $mode, ) { } } diff --git a/tests/PhpGenerator/expected/PhpNamespace.expect b/tests/PhpGenerator/expected/PhpNamespace.expect index 0577d9c4..0fd4adb9 100644 --- a/tests/PhpGenerator/expected/PhpNamespace.expect +++ b/tests/PhpGenerator/expected/PhpNamespace.expect @@ -12,7 +12,8 @@ class A implements A, C #[A] public function test( - #[\Bar\D] C $a, + #[\Bar\D] + C $a, self $b, parent $c, array $d, diff --git a/tests/PhpGenerator/expected/Printer.class-alt.expect b/tests/PhpGenerator/expected/Printer.class-alt.expect index 5fa5b6ff..bded1b70 100644 --- a/tests/PhpGenerator/expected/Printer.class-alt.expect +++ b/tests/PhpGenerator/expected/Printer.class-alt.expect @@ -64,14 +64,16 @@ final class Example extends ParentClass implements IExample public function multi( - #[Foo] $foo, + #[Foo] + $foo, ) { } public function multiType( - #[Foo] $foo, + #[Foo] + $foo, ): array { } } diff --git a/tests/PhpGenerator/expected/Printer.class.expect b/tests/PhpGenerator/expected/Printer.class.expect index c66082ba..810494c7 100644 --- a/tests/PhpGenerator/expected/Printer.class.expect +++ b/tests/PhpGenerator/expected/Printer.class.expect @@ -62,13 +62,15 @@ final class Example extends ParentClass implements IExample public function multi( - #[Foo] $foo, + #[Foo] + $foo, ) { } public function multiType( - #[Foo] $foo, + #[Foo] + $foo, ): array { } From 210059dcf0314c54e4b5f8fc156304639ac3f67b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 26 Apr 2023 12:55:09 +0200 Subject: [PATCH 162/266] Printer: multiline attributes are in own attribute block --- src/PhpGenerator/Printer.php | 1 + tests/PhpGenerator/Closure.phpt | 21 +++++++++++++++++++++ tests/PhpGenerator/Printer.function.phpt | 19 +++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 1b4fea73..cb71b3e6 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -425,6 +425,7 @@ protected function printAttributes(array $attrs, bool $inline = false): string $args = $this->dumper->format('...?:', $attr->getArguments()); $args = Helpers::simplifyTaggedNames($args, $this->namespace); $items[] = $this->printType($attr->getName(), nullable: false) . ($args ? "($args)" : ''); + $inline = $inline && !str_contains($args, "\n"); } return $inline diff --git a/tests/PhpGenerator/Closure.phpt b/tests/PhpGenerator/Closure.phpt index e511c678..33678666 100644 --- a/tests/PhpGenerator/Closure.phpt +++ b/tests/PhpGenerator/Closure.phpt @@ -84,3 +84,24 @@ same( XX, (string) $function, ); + + + +$function = new Closure; +$function->setBody('return $a + $b;'); +$function->addAttribute('Foo', ['a', str_repeat('b', 120)]); +$function->addAttribute('Bar'); + +same( + <<<'XX' + #[Foo( + 'a', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + )] + #[Bar] + function () { + return $a + $b; + } + XX, + (string) $function, +); diff --git a/tests/PhpGenerator/Printer.function.phpt b/tests/PhpGenerator/Printer.function.phpt index c75a6545..7cc301ab 100644 --- a/tests/PhpGenerator/Printer.function.phpt +++ b/tests/PhpGenerator/Printer.function.phpt @@ -162,3 +162,22 @@ Assert::match(<<<'XX' ) { } XX, $printer->printFunction($function)); + + +// parameter: multiple & multiline +$function = new GlobalFunction('func'); +$param = $function->addParameter('foo'); +$param->addAttribute('Bar'); +$param->addAttribute('Foo', ['a', str_repeat('x', 120)]); + +Assert::match(<<<'XX' + function func( + #[Bar] + #[Foo( + 'a', + 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', + )] + $foo, + ) { + } + XX, $printer->printFunction($function)); From bf3b24bddf4af8ae4bd373924916c8d62ed4a81d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sandstr=C3=B6m?= Date: Thu, 30 Mar 2023 11:50:53 +0200 Subject: [PATCH 163/266] Printer: added $singleParameterOnOneLine (#132) --- readme.md | 2 + src/PhpGenerator/Printer.php | 3 +- .../Printer.single.parameter.phpt | 88 +++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 tests/PhpGenerator/Printer.single.parameter.phpt diff --git a/readme.md b/readme.md index d6520a56..482a361a 100644 --- a/readme.md +++ b/readme.md @@ -504,6 +504,8 @@ class MyPrinter extends Nette\PhpGenerator\Printer public int $linesBetweenUseTypes = 0; /** position of the opening brace for functions and methods */ public bool $bracesOnNextLine = true; + /** place one parameter in one line, even if it has an attribute or is promoted */ + public bool $singleParameterOnOneLine = false; /** separator between the right parenthesis and return type of functions and methods */ public string $returnTypeColon = ': '; } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index cb71b3e6..b3f380c8 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -27,6 +27,7 @@ class Printer public int $linesBetweenUseTypes = 0; public string $returnTypeColon = ': '; public bool $bracesOnNextLine = true; + public bool $singleParameterOnOneLine = false; protected ?PhpNamespace $namespace = null; protected ?Dumper $dumper; private bool $resolveTypes = true; @@ -332,7 +333,7 @@ protected function printParameters(Closure|GlobalFunction|Method $function, int $special = $special || $param instanceof PromotedParameter || $param->getAttributes(); } - if (!$special) { + if (!$special || ($this->singleParameterOnOneLine && count($function->getParameters()) === 1)) { $line = $this->formatParameters($function, false); if (!str_contains($line, "\n") && strlen($line) + $column <= $this->wrapLength) { return $line; diff --git a/tests/PhpGenerator/Printer.single.parameter.phpt b/tests/PhpGenerator/Printer.single.parameter.phpt new file mode 100644 index 00000000..0fa9fe39 --- /dev/null +++ b/tests/PhpGenerator/Printer.single.parameter.phpt @@ -0,0 +1,88 @@ +singleParameterOnOneLine = true; + + +$function = new Nette\PhpGenerator\GlobalFunction('singleFunction'); +$function + ->setReturnType('array') + ->addParameter('foo') + ->addAttribute('Foo'); + +Assert::match(<<<'XX' + function singleFunction(#[Foo] $foo): array + { + } + + XX, $printer->printFunction($function)); + + +$method = new Nette\PhpGenerator\Method('singleMethod'); +$method + ->setPublic() + ->setReturnType('array') + ->addParameter('foo') + ->addAttribute('Foo'); + +Assert::match(<<<'XX' + public function singleMethod(#[Foo] $foo): array + { + } + + XX, $printer->printMethod($method)); + + +$method = new Nette\PhpGenerator\Method('singleMethod'); +$method + ->setPublic() + ->setReturnType('array') + ->addPromotedParameter('foo') + ->setPublic(); + +Assert::match(<<<'XX' + public function singleMethod(public $foo): array + { + } + + XX, $printer->printMethod($method)); + + +$method = new Nette\PhpGenerator\Method('singleMethod'); +$method + ->setPublic() + ->setReturnType('array') + ->addPromotedParameter('looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong') + ->setPublic(); + +Assert::match(<<<'XX' + public function singleMethod( + public $looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, + ): array + { + } + + XX, $printer->printMethod($method)); + + +$method = new Nette\PhpGenerator\Method('singleMethod'); +$method->addParameter('foo') + ->addAttribute('Foo', [new Literal("'\n'")]); + +Assert::match(<<<'XX' + function singleMethod( + #[Foo(' + ')] + $foo, + ) { + } + XX, $printer->printMethod($method)); From de1843fbb692125e307937c85d43937d0dc0c1d4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 26 Apr 2023 17:03:46 +0200 Subject: [PATCH 164/266] fixed class resolving in trait usages [Closes #128] --- src/PhpGenerator/Extractor.php | 2 +- src/PhpGenerator/Printer.php | 5 +++-- tests/PhpGenerator/expected/Extractor.traits.expect | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index f0f2abab..9daeeabd 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -299,7 +299,7 @@ private function addTraitToClass(ClassLike $class, Node\Stmt\TraitUse $node): vo } foreach ($node->adaptations as $item) { - $trait->addResolution(trim($this->toPhp($item), ';')); + $trait->addResolution(rtrim($this->getReformattedContents([$item], 0), ';')); } $this->addCommentAndAttributes($trait, $node); diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index b3f380c8..1c5ee6bf 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -147,11 +147,12 @@ public function printClass( $traits = []; if ($class instanceof ClassType || $class instanceof TraitType || $class instanceof EnumType) { foreach ($class->getTraits() as $trait) { - $resolutions = $trait->getResolutions(); + $resolutions = implode(";\n", $trait->getResolutions()); + $resolutions = Helpers::simplifyTaggedNames($resolutions, $this->namespace); $traits[] = $this->printDocComment($trait) . 'use ' . $resolver($trait->getName()) . ($resolutions - ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n" + ? " {\n" . $this->indent($resolutions) . ";\n}\n" : ";\n"); } } diff --git a/tests/PhpGenerator/expected/Extractor.traits.expect b/tests/PhpGenerator/expected/Extractor.traits.expect index fa2e8c05..1f55b791 100644 --- a/tests/PhpGenerator/expected/Extractor.traits.expect +++ b/tests/PhpGenerator/expected/Extractor.traits.expect @@ -101,8 +101,8 @@ class Class5 { use Trait1; use Trait1b { - \Trait1b::f1 insteadof \Trait1; + Trait1b::f1 insteadof Trait1; // not yet supported - \Trait1b::f1 as private; + Trait1b::f1 as private; } } From b2aebf0903a3ae0a97b391e301a1f98d6aec2a26 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 22 May 2023 14:07:33 +0200 Subject: [PATCH 165/266] readme: improved info about PSR --- readme.md | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/readme.md b/readme.md index 482a361a..64463565 100644 --- a/readme.md +++ b/readme.md @@ -477,40 +477,55 @@ function foo($a) Printers and PSR Compliance --------------------------- -PHP code is generated by `Printer` objects. There is a `PsrPrinter` whose output conforms to PSR-2 and PSR-12 and uses spaces for indentation, and a `Printer` that uses tabs for indentation. +The [Printer](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Printer.html) class is used to generate PHP code: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // same as: echo $class +``` + +It can generate code for all other elements, offering methods such as `printFunction()`, `printNamespace()`, etc. + +Additionally, the `PsrPrinter` class is available, whose output is in compliance with the PSR-2 / PSR-12 / PER coding style: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 spaces indentation +echo $printer->printClass($class); ``` -Need to customize printer behavior? Create your own by inheriting the `Printer` class. You can reconfigure these variables: +Need to fine-tune behavior to your needs? Create your own printer by inheriting from the `Printer` class. You can reconfigure these variables: ```php class MyPrinter extends Nette\PhpGenerator\Printer { - /** length of the line after which the line will break */ + // length of the line after which the line will break public int $wrapLength = 120; - /** indentation character, can be replaced with a sequence of spaces */ + // indentation character, can be replaced with a sequence of spaces public string $indentation = "\t"; - /** number of blank lines between properties */ + // number of blank lines between properties public int $linesBetweenProperties = 0; - /** number of blank lines between methods */ + // number of blank lines between methods public int $linesBetweenMethods = 2; - /** number of blank lines between groups of use statements for classes, functions, and constants */ + // number of blank lines between groups of use statements for classes, functions, and constants public int $linesBetweenUseTypes = 0; - /** position of the opening brace for functions and methods */ + // position of the opening brace for functions and methods public bool $bracesOnNextLine = true; - /** place one parameter in one line, even if it has an attribute or is promoted */ + // place one parameter in one line, even if it has an attribute or is promoted public bool $singleParameterOnOneLine = false; - /** separator between the right parenthesis and return type of functions and methods */ + // separator between the right parenthesis and return type of functions and methods public string $returnTypeColon = ': '; } ``` +How and why exactly does the standard `Printer` and `PsrPrinter` differ? Why isn't there just one printer, the `PsrPrinter`, in the package? + +The standard `Printer` formats the code as we do it in all of Nette. Since Nette was created much earlier than PSR, and also because PSR for many years did not deliver standards in time, but sometimes even with several years of delay from the introduction of a new feature in PHP, this resulted in a few minor differences in the coding standard. +The bigger difference is just the use of tabs instead of spaces. We know that by using tabs in our projects we allow for width adjustment, which is [essential for people with visual impairments](https://doc.nette.org/en/contributing/coding-standard#toc-tabs-instead-of-spaces). +An example of a minor difference is the placement of the curly brace on a separate line for functions and methods and always. We see the PSR recommendation as illogical and [leading to a decrease in code clarity](https://doc.nette.org/en/contributing/coding-standard#toc-wrapping-and-braces). + Types ----- From bdb3fc6e5e58d36535520b82230e6d3d6bf2d604 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 30 Jul 2023 13:30:56 +0200 Subject: [PATCH 166/266] refactoring --- src/PhpGenerator/Factory.php | 2 +- src/PhpGenerator/Traits/MethodsAware.php | 7 +------ src/PhpGenerator/Traits/PropertiesAware.php | 6 +----- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 33c6f115..db5fa118 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -14,7 +14,7 @@ /** - * Creates a representation based on reflection. + * Creates a representations based on reflection or source code. */ final class Factory { diff --git a/src/PhpGenerator/Traits/MethodsAware.php b/src/PhpGenerator/Traits/MethodsAware.php index 76e50f34..a28c0613 100644 --- a/src/PhpGenerator/Traits/MethodsAware.php +++ b/src/PhpGenerator/Traits/MethodsAware.php @@ -49,12 +49,7 @@ public function getMethods(): array public function getMethod(string $name): Method { - $m = $this->methods[strtolower($name)] ?? null; - if (!$m) { - throw new Nette\InvalidArgumentException("Method '$name' not found."); - } - - return $m; + return $this->methods[strtolower($name)] ?? throw new Nette\InvalidArgumentException("Method '$name' not found."); } diff --git a/src/PhpGenerator/Traits/PropertiesAware.php b/src/PhpGenerator/Traits/PropertiesAware.php index ead392de..2bc958cf 100644 --- a/src/PhpGenerator/Traits/PropertiesAware.php +++ b/src/PhpGenerator/Traits/PropertiesAware.php @@ -44,11 +44,7 @@ public function getProperties(): array public function getProperty(string $name): Property { - if (!isset($this->properties[$name])) { - throw new Nette\InvalidArgumentException("Property '$name' not found."); - } - - return $this->properties[$name]; + return $this->properties[$name] ?? throw new Nette\InvalidArgumentException("Property '$name' not found."); } From d34e60d62e6f7c759ecacd8dc1a667cfbb1ec7f7 Mon Sep 17 00:00:00 2001 From: Sander Date: Sun, 30 Jul 2023 13:17:37 +0200 Subject: [PATCH 167/266] ConstantsAware: added hasConstant() (#133) --- src/PhpGenerator/Traits/ConstantsAware.php | 6 ++++++ tests/PhpGenerator/ClassType.phpt | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/PhpGenerator/Traits/ConstantsAware.php b/src/PhpGenerator/Traits/ConstantsAware.php index f048e355..12968fc7 100644 --- a/src/PhpGenerator/Traits/ConstantsAware.php +++ b/src/PhpGenerator/Traits/ConstantsAware.php @@ -62,4 +62,10 @@ public function removeConstant(string $name): static unset($this->consts[$name]); return $this; } + + + public function hasConstant(string $name): bool + { + return isset($this->consts[$name]); + } } diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index 8fb756e9..6e1a4f45 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -41,6 +41,8 @@ $class->addConstant('ROLE', 'admin'); $class->addConstant('ACTIVE', false) ->setFinal() ->setType('bool'); +Assert::true($class->hasConstant('ROLE')); +Assert::false($class->hasConstant('xxx')); Assert::false($class->isFinal()); Assert::true($class->isAbstract()); From 43c9b67cc7181e578498fbed5873aef78540e2f6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 30 Jul 2023 13:21:01 +0200 Subject: [PATCH 168/266] FunctionLike: added getParameter() & hasParameter() --- src/PhpGenerator/Traits/FunctionLike.php | 12 ++++++++++++ tests/PhpGenerator/ClassType.phpt | 5 ++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index c0b33f99..7a92a8db 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -85,6 +85,12 @@ public function getParameters(): array } + public function getParameter(string $name): Parameter + { + return $this->parameters[$name] ?? throw new Nette\InvalidArgumentException("Parameter '$name' not found."); + } + + /** * @param string $name without $ */ @@ -109,6 +115,12 @@ public function removeParameter(string $name): static } + public function hasParameter(string $name): bool + { + return isset($this->parameters[$name]); + } + + public function setVariadic(bool $state = true): static { $this->variadic = $state; diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index 6e1a4f45..660399b5 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -124,8 +124,11 @@ Assert::false($m->isPublic()); $method = $class->addMethod('show') ->setAbstract(); -$method->addParameter('foo'); +$p = $method->addParameter('foo'); +Assert::true($method->hasParameter('foo')); +Assert::same($p, $method->getParameter('foo')); $method->removeParameter('foo'); +Assert::false($method->hasParameter('foo')); $method->addParameter('item'); From ea40f2f8d743d5ca5a203f2ece4f80940aba115d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 30 Jul 2023 12:55:12 +0200 Subject: [PATCH 169/266] Factory: properties in readonly classes are not readonly --- src/PhpGenerator/Factory.php | 2 +- tests/PhpGenerator/ClassType.readonly.phpt | 24 +++++++++++++++++++ .../expected/ClassType.from.82.expect | 9 +++++++ .../expected/Extractor.classes.82.expect | 9 +++++++ tests/PhpGenerator/fixtures/classes.82.php | 8 +++++++ 5 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 tests/PhpGenerator/ClassType.readonly.phpt diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index db5fa118..61b24dca 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -265,7 +265,7 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property $prop->setType((string) $from->getType()); $prop->setInitialized($from->hasType() && array_key_exists($prop->getName(), $defaults)); - $prop->setReadOnly(PHP_VERSION_ID >= 80100 ? $from->isReadOnly() : false); + $prop->setReadOnly(PHP_VERSION_ID >= 80100 && $from->isReadOnly() && !(PHP_VERSION_ID >= 80200 && $from->getDeclaringClass()->isReadOnly())); $prop->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); $prop->setAttributes($this->getAttributes($from)); return $prop; diff --git a/tests/PhpGenerator/ClassType.readonly.phpt b/tests/PhpGenerator/ClassType.readonly.phpt new file mode 100644 index 00000000..afa98fe7 --- /dev/null +++ b/tests/PhpGenerator/ClassType.readonly.phpt @@ -0,0 +1,24 @@ +getProperty('foo')->isReadOnly()); +Assert::false($class->getMethod('__construct')->getParameter('bar')->isReadOnly()); + +$file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.82.php')))->extractAll(); +$class = $file->getClasses()[Abc\Class13::class]; +Assert::false($class->getProperty('foo')->isReadOnly()); +Assert::false($class->getMethod('__construct')->getParameter('bar')->isReadOnly()); diff --git a/tests/PhpGenerator/expected/ClassType.from.82.expect b/tests/PhpGenerator/expected/ClassType.from.82.expect index 6ea6378a..894f1a20 100644 --- a/tests/PhpGenerator/expected/ClassType.from.82.expect +++ b/tests/PhpGenerator/expected/ClassType.from.82.expect @@ -1,5 +1,14 @@ readonly class Class13 { + public bool $foo; + + + public function __construct( + public bool $bar = true, + ) { + } + + public function func(C|(X&D)|null $foo): (A&B)|null { } diff --git a/tests/PhpGenerator/expected/Extractor.classes.82.expect b/tests/PhpGenerator/expected/Extractor.classes.82.expect index 54f5c525..556d21dd 100644 --- a/tests/PhpGenerator/expected/Extractor.classes.82.expect +++ b/tests/PhpGenerator/expected/Extractor.classes.82.expect @@ -6,6 +6,15 @@ namespace Abc; readonly class Class13 { + public bool $foo; + + + public function __construct( + private bool $bar = true, + ) { + } + + public function func(C|(X&D)|null $foo): (A&B)|null { } diff --git a/tests/PhpGenerator/fixtures/classes.82.php b/tests/PhpGenerator/fixtures/classes.82.php index ae22c11e..5ad295b7 100644 --- a/tests/PhpGenerator/fixtures/classes.82.php +++ b/tests/PhpGenerator/fixtures/classes.82.php @@ -6,6 +6,14 @@ readonly class Class13 { + public bool $foo; + + + public function __construct(private bool $bar = true) + { + } + + public function func(C|(X&D)|null $foo): (A&B)|null { } From d9157df8463b198dcbcd9f979fb09a9367c7fe99 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 30 Jul 2023 14:01:32 +0200 Subject: [PATCH 170/266] support for PHP 8.3 --- .github/workflows/tests.yml | 2 +- composer.json | 2 +- readme.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 239bdc4d..58b8cf7c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['8.0', '8.1', '8.2'] + php: ['8.0', '8.1', '8.2', '8.3'] fail-fast: false diff --git a/composer.json b/composer.json index e8dda3c2..179e5835 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=8.0 <8.3", + "php": ">=8.0 <8.4", "nette/utils": "^3.2.9 || ^4.0" }, "require-dev": { diff --git a/readme.md b/readme.md index 64463565..c3ea384d 100644 --- a/readme.md +++ b/readme.md @@ -846,7 +846,7 @@ echo $dumper->dump($var); // prints ['a', 'b', 123] Compatibility Table ------------------- -- PhpGenerator 4.0 is compatible with PHP 8.0 to 8.2 +- PhpGenerator 4.0 is compatible with PHP 8.0 to 8.3 - PhpGenerator 3.6 is compatible with PHP 7.2 to 8.2 - PhpGenerator 3.2 – 3.5 is compatible with PHP 7.1 to 8.0 - PhpGenerator 3.1 is compatible with PHP 7.1 to 7.3 From a364e2f7e994d26272deeb7ccb134c32a1130278 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 7 Aug 2023 01:50:21 +0200 Subject: [PATCH 171/266] cs --- tests/PhpGenerator/ClassType.addMember.phpt | 1 - tests/PhpGenerator/ClassType.attributes.phpt | 1 - tests/PhpGenerator/ClassType.clone.phpt | 1 - tests/PhpGenerator/ClassType.from.81.phpt | 1 - tests/PhpGenerator/ClassType.from.82.phpt | 1 - tests/PhpGenerator/ClassType.from.anonymous.phpt | 1 - tests/PhpGenerator/ClassType.from.bodies.phpt | 1 - tests/PhpGenerator/ClassType.from.phpt | 1 - tests/PhpGenerator/ClassType.from.trait.phpt | 1 - tests/PhpGenerator/ClassType.inheritance.phpt | 1 - tests/PhpGenerator/ClassType.phpt | 1 - tests/PhpGenerator/ClassType.promotion.phpt | 1 - tests/PhpGenerator/ClassType.readonly.phpt | 1 - tests/PhpGenerator/ClassType.validate.phpt | 1 - tests/PhpGenerator/Closure.from.phpt | 1 - tests/PhpGenerator/Closure.long.phpt | 1 - tests/PhpGenerator/Closure.phpt | 1 - tests/PhpGenerator/Dumper.dump().enum.phpt | 1 - tests/PhpGenerator/Dumper.dump().errors.phpt | 1 - tests/PhpGenerator/Dumper.dump().indent.phpt | 1 - tests/PhpGenerator/Dumper.dump().phpt | 1 - tests/PhpGenerator/Dumper.dump().wrap.phpt | 1 - tests/PhpGenerator/Dumper.format().phpt | 1 - tests/PhpGenerator/Dumper.format().wrap.phpt | 1 - tests/PhpGenerator/EnumType.from.phpt | 1 - tests/PhpGenerator/EnumType.phpt | 1 - tests/PhpGenerator/Extractor.extractAll.phpt | 1 - tests/PhpGenerator/Extractor.extractAll.resolving.phpt | 1 - tests/PhpGenerator/Extractor.getFunctionBody.phpt | 1 - tests/PhpGenerator/Extractor.getMethodBodies.phpt | 1 - tests/PhpGenerator/Factory.fromClassCode.phpt | 1 - tests/PhpGenerator/Factory.phpt | 1 - tests/PhpGenerator/GlobalFunction.from.phpt | 1 - tests/PhpGenerator/GlobalFunction.phpt | 1 - tests/PhpGenerator/Helpers.comments.phpt | 1 - tests/PhpGenerator/Helpers.isIdentifier.phpt | 1 - tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt | 1 - tests/PhpGenerator/Helpers.tabsToSpaces().phpt | 1 - tests/PhpGenerator/Helpers.unindent.phpt | 1 - tests/PhpGenerator/Helpers.validateType.phpt | 1 - tests/PhpGenerator/InterfaceType.fromCode.phpt | 1 - tests/PhpGenerator/InterfaceType.phpt | 1 - tests/PhpGenerator/Method.longParams.phpt | 1 - tests/PhpGenerator/Method.scalarParameters.phpt | 1 - tests/PhpGenerator/Method.validate.phpt | 1 - tests/PhpGenerator/Method.variadics.phpt | 1 - tests/PhpGenerator/NameAware.cloneWithName.phpt | 1 - tests/PhpGenerator/PhpFile.addNamespace.phpt | 1 - tests/PhpGenerator/PhpFile.phpt | 1 - tests/PhpGenerator/PhpNamespace.add.phpt | 1 - tests/PhpGenerator/PhpNamespace.aliases.phpt | 1 - tests/PhpGenerator/PhpNamespace.fqn.phpt | 1 - tests/PhpGenerator/PhpNamespace.phpt | 1 - tests/PhpGenerator/PhpNamespace.print.phpt | 1 - tests/PhpGenerator/PhpNamespace.resolve.phpt | 1 - tests/PhpGenerator/PhpNamespace.simplify.phpt | 1 - tests/PhpGenerator/Printer.arrow.phpt | 1 - tests/PhpGenerator/Printer.function.phpt | 1 - tests/PhpGenerator/Printer.namespace.phpt | 1 - tests/PhpGenerator/Printer.phpt | 1 - tests/PhpGenerator/Printer.single.parameter.phpt | 1 - tests/PhpGenerator/Printer.use-order.phpt | 1 - tests/PhpGenerator/PsrPrinter.phpt | 1 - tests/PhpGenerator/Type.phpt | 1 - tests/PhpGenerator/invalidNames.phpt | 1 - 65 files changed, 65 deletions(-) diff --git a/tests/PhpGenerator/ClassType.addMember.phpt b/tests/PhpGenerator/ClassType.addMember.phpt index c173021e..95ee3654 100644 --- a/tests/PhpGenerator/ClassType.addMember.phpt +++ b/tests/PhpGenerator/ClassType.addMember.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/ClassType.attributes.phpt b/tests/PhpGenerator/ClassType.attributes.phpt index bcfaf4c2..1ea41b09 100644 --- a/tests/PhpGenerator/ClassType.attributes.phpt +++ b/tests/PhpGenerator/ClassType.attributes.phpt @@ -9,7 +9,6 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; use Nette\PhpGenerator\Literal; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/ClassType.clone.phpt b/tests/PhpGenerator/ClassType.clone.phpt index b38f1c9d..0d632bfe 100644 --- a/tests/PhpGenerator/ClassType.clone.phpt +++ b/tests/PhpGenerator/ClassType.clone.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/ClassType.from.81.phpt b/tests/PhpGenerator/ClassType.from.81.phpt index dbdd5659..6dd3db33 100644 --- a/tests/PhpGenerator/ClassType.from.81.phpt +++ b/tests/PhpGenerator/ClassType.from.81.phpt @@ -8,7 +8,6 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; - require __DIR__ . '/../bootstrap.php'; require __DIR__ . '/fixtures/classes.81.php'; diff --git a/tests/PhpGenerator/ClassType.from.82.phpt b/tests/PhpGenerator/ClassType.from.82.phpt index db001ac7..eb171d5d 100644 --- a/tests/PhpGenerator/ClassType.from.82.phpt +++ b/tests/PhpGenerator/ClassType.from.82.phpt @@ -8,7 +8,6 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; - require __DIR__ . '/../bootstrap.php'; require __DIR__ . '/fixtures/classes.82.php'; diff --git a/tests/PhpGenerator/ClassType.from.anonymous.phpt b/tests/PhpGenerator/ClassType.from.anonymous.phpt index 8c5192a2..c6de08bb 100644 --- a/tests/PhpGenerator/ClassType.from.anonymous.phpt +++ b/tests/PhpGenerator/ClassType.from.anonymous.phpt @@ -8,7 +8,6 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/ClassType.from.bodies.phpt b/tests/PhpGenerator/ClassType.from.bodies.phpt index 40358a97..302f305b 100644 --- a/tests/PhpGenerator/ClassType.from.bodies.phpt +++ b/tests/PhpGenerator/ClassType.from.bodies.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; require __DIR__ . '/fixtures/bodies.php'; diff --git a/tests/PhpGenerator/ClassType.from.phpt b/tests/PhpGenerator/ClassType.from.phpt index 837fb11a..43734a5e 100644 --- a/tests/PhpGenerator/ClassType.from.phpt +++ b/tests/PhpGenerator/ClassType.from.phpt @@ -9,7 +9,6 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; use Nette\PhpGenerator\Factory; - require __DIR__ . '/../bootstrap.php'; require __DIR__ . '/fixtures/classes.php'; diff --git a/tests/PhpGenerator/ClassType.from.trait.phpt b/tests/PhpGenerator/ClassType.from.trait.phpt index d810b851..3fdf8b24 100644 --- a/tests/PhpGenerator/ClassType.from.trait.phpt +++ b/tests/PhpGenerator/ClassType.from.trait.phpt @@ -4,7 +4,6 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; - require __DIR__ . '/../bootstrap.php'; require __DIR__ . '/fixtures/traits.php'; diff --git a/tests/PhpGenerator/ClassType.inheritance.phpt b/tests/PhpGenerator/ClassType.inheritance.phpt index bcde532d..0455dc17 100644 --- a/tests/PhpGenerator/ClassType.inheritance.phpt +++ b/tests/PhpGenerator/ClassType.inheritance.phpt @@ -4,7 +4,6 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index 660399b5..2c87f0ca 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -11,7 +11,6 @@ use Nette\PhpGenerator\Literal; use Nette\PhpGenerator\Type; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/ClassType.promotion.phpt b/tests/PhpGenerator/ClassType.promotion.phpt index a76f4045..ba4944ee 100644 --- a/tests/PhpGenerator/ClassType.promotion.phpt +++ b/tests/PhpGenerator/ClassType.promotion.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; use Nette\PhpGenerator\Literal; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/ClassType.readonly.phpt b/tests/PhpGenerator/ClassType.readonly.phpt index afa98fe7..07817b4c 100644 --- a/tests/PhpGenerator/ClassType.readonly.phpt +++ b/tests/PhpGenerator/ClassType.readonly.phpt @@ -10,7 +10,6 @@ use Nette\PhpGenerator\ClassType; use Nette\PhpGenerator\Extractor; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; require __DIR__ . '/fixtures/classes.82.php'; diff --git a/tests/PhpGenerator/ClassType.validate.phpt b/tests/PhpGenerator/ClassType.validate.phpt index 416f282f..874597c1 100644 --- a/tests/PhpGenerator/ClassType.validate.phpt +++ b/tests/PhpGenerator/ClassType.validate.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Closure.from.phpt b/tests/PhpGenerator/Closure.from.phpt index 0d0f7662..aab8faba 100644 --- a/tests/PhpGenerator/Closure.from.phpt +++ b/tests/PhpGenerator/Closure.from.phpt @@ -4,7 +4,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Closure; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Closure.long.phpt b/tests/PhpGenerator/Closure.long.phpt index 2ab24ea4..b70ac49f 100644 --- a/tests/PhpGenerator/Closure.long.phpt +++ b/tests/PhpGenerator/Closure.long.phpt @@ -4,7 +4,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Closure; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Closure.phpt b/tests/PhpGenerator/Closure.phpt index 33678666..e36bfc60 100644 --- a/tests/PhpGenerator/Closure.phpt +++ b/tests/PhpGenerator/Closure.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Closure; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Dumper.dump().enum.phpt b/tests/PhpGenerator/Dumper.dump().enum.phpt index 0963c169..4a193474 100644 --- a/tests/PhpGenerator/Dumper.dump().enum.phpt +++ b/tests/PhpGenerator/Dumper.dump().enum.phpt @@ -10,7 +10,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Dumper; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Dumper.dump().errors.phpt b/tests/PhpGenerator/Dumper.dump().errors.phpt index a5b5dd59..722d50d8 100644 --- a/tests/PhpGenerator/Dumper.dump().errors.phpt +++ b/tests/PhpGenerator/Dumper.dump().errors.phpt @@ -9,7 +9,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Dumper; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Dumper.dump().indent.phpt b/tests/PhpGenerator/Dumper.dump().indent.phpt index 1ad81c65..c3e9e970 100644 --- a/tests/PhpGenerator/Dumper.dump().indent.phpt +++ b/tests/PhpGenerator/Dumper.dump().indent.phpt @@ -10,7 +10,6 @@ use Nette\PhpGenerator\Dumper; use Nette\PhpGenerator\Literal; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index 67e17b5c..6fb4467c 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -10,7 +10,6 @@ use Nette\PhpGenerator\Dumper; use Nette\PhpGenerator\Literal; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; ini_set('serialize_precision', '14'); diff --git a/tests/PhpGenerator/Dumper.dump().wrap.phpt b/tests/PhpGenerator/Dumper.dump().wrap.phpt index 01471654..d0e9ca12 100644 --- a/tests/PhpGenerator/Dumper.dump().wrap.phpt +++ b/tests/PhpGenerator/Dumper.dump().wrap.phpt @@ -9,7 +9,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Dumper; use Nette\PhpGenerator\Literal; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Dumper.format().phpt b/tests/PhpGenerator/Dumper.format().phpt index 8400a9e3..c8b79fde 100644 --- a/tests/PhpGenerator/Dumper.format().phpt +++ b/tests/PhpGenerator/Dumper.format().phpt @@ -9,7 +9,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Dumper; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Dumper.format().wrap.phpt b/tests/PhpGenerator/Dumper.format().wrap.phpt index 05437587..5eb1e38e 100644 --- a/tests/PhpGenerator/Dumper.format().wrap.phpt +++ b/tests/PhpGenerator/Dumper.format().wrap.phpt @@ -9,7 +9,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Dumper; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/EnumType.from.phpt b/tests/PhpGenerator/EnumType.from.phpt index 8e1ded52..7dadafd2 100644 --- a/tests/PhpGenerator/EnumType.from.phpt +++ b/tests/PhpGenerator/EnumType.from.phpt @@ -8,7 +8,6 @@ declare(strict_types=1); use Nette\PhpGenerator\EnumType; - require __DIR__ . '/../bootstrap.php'; require __DIR__ . '/fixtures/enum.php'; diff --git a/tests/PhpGenerator/EnumType.phpt b/tests/PhpGenerator/EnumType.phpt index eefb7a7f..3c919ea9 100644 --- a/tests/PhpGenerator/EnumType.phpt +++ b/tests/PhpGenerator/EnumType.phpt @@ -9,7 +9,6 @@ declare(strict_types=1); use Nette\PhpGenerator\EnumType; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Extractor.extractAll.phpt b/tests/PhpGenerator/Extractor.extractAll.phpt index a04edc8d..939a194e 100644 --- a/tests/PhpGenerator/Extractor.extractAll.phpt +++ b/tests/PhpGenerator/Extractor.extractAll.phpt @@ -4,7 +4,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Extractor; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Extractor.extractAll.resolving.phpt b/tests/PhpGenerator/Extractor.extractAll.resolving.phpt index 65d720ea..20affda6 100644 --- a/tests/PhpGenerator/Extractor.extractAll.resolving.phpt +++ b/tests/PhpGenerator/Extractor.extractAll.resolving.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Extractor; use Nette\PhpGenerator\PhpNamespace; use Nette\PhpGenerator\Printer; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Extractor.getFunctionBody.phpt b/tests/PhpGenerator/Extractor.getFunctionBody.phpt index aef8a994..ae81dc22 100644 --- a/tests/PhpGenerator/Extractor.getFunctionBody.phpt +++ b/tests/PhpGenerator/Extractor.getFunctionBody.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Extractor; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Extractor.getMethodBodies.phpt b/tests/PhpGenerator/Extractor.getMethodBodies.phpt index bf073709..a499442d 100644 --- a/tests/PhpGenerator/Extractor.getMethodBodies.phpt +++ b/tests/PhpGenerator/Extractor.getMethodBodies.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Extractor; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Factory.fromClassCode.phpt b/tests/PhpGenerator/Factory.fromClassCode.phpt index 373256c4..2b1ca92b 100644 --- a/tests/PhpGenerator/Factory.fromClassCode.phpt +++ b/tests/PhpGenerator/Factory.fromClassCode.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Factory; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Factory.phpt b/tests/PhpGenerator/Factory.phpt index d1690398..0dff2244 100644 --- a/tests/PhpGenerator/Factory.phpt +++ b/tests/PhpGenerator/Factory.phpt @@ -9,7 +9,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Factory; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/GlobalFunction.from.phpt b/tests/PhpGenerator/GlobalFunction.from.phpt index 2bef0649..6bd1eab9 100644 --- a/tests/PhpGenerator/GlobalFunction.from.phpt +++ b/tests/PhpGenerator/GlobalFunction.from.phpt @@ -4,7 +4,6 @@ declare(strict_types=1); use Nette\PhpGenerator\GlobalFunction; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/GlobalFunction.phpt b/tests/PhpGenerator/GlobalFunction.phpt index 6e06fd9b..55727d99 100644 --- a/tests/PhpGenerator/GlobalFunction.phpt +++ b/tests/PhpGenerator/GlobalFunction.phpt @@ -4,7 +4,6 @@ declare(strict_types=1); use Nette\PhpGenerator\GlobalFunction; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Helpers.comments.phpt b/tests/PhpGenerator/Helpers.comments.phpt index e46eab20..2d4c6689 100644 --- a/tests/PhpGenerator/Helpers.comments.phpt +++ b/tests/PhpGenerator/Helpers.comments.phpt @@ -9,7 +9,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Helpers; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Helpers.isIdentifier.phpt b/tests/PhpGenerator/Helpers.isIdentifier.phpt index eb9ceda6..70b394dd 100644 --- a/tests/PhpGenerator/Helpers.isIdentifier.phpt +++ b/tests/PhpGenerator/Helpers.isIdentifier.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Helpers; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt b/tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt index b827ea1c..f0c1febc 100644 --- a/tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt +++ b/tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Helpers; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Helpers.tabsToSpaces().phpt b/tests/PhpGenerator/Helpers.tabsToSpaces().phpt index 7b373f6a..be79c31d 100644 --- a/tests/PhpGenerator/Helpers.tabsToSpaces().phpt +++ b/tests/PhpGenerator/Helpers.tabsToSpaces().phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Helpers; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Helpers.unindent.phpt b/tests/PhpGenerator/Helpers.unindent.phpt index dba75a68..02161321 100644 --- a/tests/PhpGenerator/Helpers.unindent.phpt +++ b/tests/PhpGenerator/Helpers.unindent.phpt @@ -9,7 +9,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Helpers; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Helpers.validateType.phpt b/tests/PhpGenerator/Helpers.validateType.phpt index 1277ca8f..850c637f 100644 --- a/tests/PhpGenerator/Helpers.validateType.phpt +++ b/tests/PhpGenerator/Helpers.validateType.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Helpers; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/InterfaceType.fromCode.phpt b/tests/PhpGenerator/InterfaceType.fromCode.phpt index 34272daa..a0b672f2 100644 --- a/tests/PhpGenerator/InterfaceType.fromCode.phpt +++ b/tests/PhpGenerator/InterfaceType.fromCode.phpt @@ -4,7 +4,6 @@ declare(strict_types=1); use Nette\PhpGenerator\InterfaceType; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/InterfaceType.phpt b/tests/PhpGenerator/InterfaceType.phpt index 0fccbaa9..a208db5e 100644 --- a/tests/PhpGenerator/InterfaceType.phpt +++ b/tests/PhpGenerator/InterfaceType.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\InterfaceType; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Method.longParams.phpt b/tests/PhpGenerator/Method.longParams.phpt index a3a1fd2f..b0b09d45 100644 --- a/tests/PhpGenerator/Method.longParams.phpt +++ b/tests/PhpGenerator/Method.longParams.phpt @@ -3,7 +3,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Method; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Method.scalarParameters.phpt b/tests/PhpGenerator/Method.scalarParameters.phpt index a121d71d..fc885857 100644 --- a/tests/PhpGenerator/Method.scalarParameters.phpt +++ b/tests/PhpGenerator/Method.scalarParameters.phpt @@ -11,7 +11,6 @@ use Nette\PhpGenerator\Method; use Nette\PhpGenerator\Type; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; // test from diff --git a/tests/PhpGenerator/Method.validate.phpt b/tests/PhpGenerator/Method.validate.phpt index 46112656..4af92226 100644 --- a/tests/PhpGenerator/Method.validate.phpt +++ b/tests/PhpGenerator/Method.validate.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Method; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Method.variadics.phpt b/tests/PhpGenerator/Method.variadics.phpt index beea8f02..376257ad 100644 --- a/tests/PhpGenerator/Method.variadics.phpt +++ b/tests/PhpGenerator/Method.variadics.phpt @@ -9,7 +9,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Method; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/NameAware.cloneWithName.phpt b/tests/PhpGenerator/NameAware.cloneWithName.phpt index 0faa5ab5..2a40006b 100644 --- a/tests/PhpGenerator/NameAware.cloneWithName.phpt +++ b/tests/PhpGenerator/NameAware.cloneWithName.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/PhpFile.addNamespace.phpt b/tests/PhpGenerator/PhpFile.addNamespace.phpt index d8aa4946..6854b7b3 100644 --- a/tests/PhpGenerator/PhpFile.addNamespace.phpt +++ b/tests/PhpGenerator/PhpFile.addNamespace.phpt @@ -4,7 +4,6 @@ declare(strict_types=1); use Nette\PhpGenerator\PhpFile; use Nette\PhpGenerator\PhpNamespace; - require __DIR__ . '/../bootstrap.php'; $namespace = new PhpNamespace('Foo'); diff --git a/tests/PhpGenerator/PhpFile.phpt b/tests/PhpGenerator/PhpFile.phpt index 804224e4..9142aa45 100644 --- a/tests/PhpGenerator/PhpFile.phpt +++ b/tests/PhpGenerator/PhpFile.phpt @@ -8,7 +8,6 @@ declare(strict_types=1); use Nette\PhpGenerator\PhpFile; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/PhpNamespace.add.phpt b/tests/PhpGenerator/PhpNamespace.add.phpt index 8f2d6341..2236292f 100644 --- a/tests/PhpGenerator/PhpNamespace.add.phpt +++ b/tests/PhpGenerator/PhpNamespace.add.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; use Nette\PhpGenerator\PhpNamespace; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/PhpNamespace.aliases.phpt b/tests/PhpGenerator/PhpNamespace.aliases.phpt index fbe5b665..d7dd1358 100644 --- a/tests/PhpGenerator/PhpNamespace.aliases.phpt +++ b/tests/PhpGenerator/PhpNamespace.aliases.phpt @@ -4,7 +4,6 @@ declare(strict_types=1); use Nette\PhpGenerator\PhpNamespace; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/PhpNamespace.fqn.phpt b/tests/PhpGenerator/PhpNamespace.fqn.phpt index 5615e6c5..28a8c745 100644 --- a/tests/PhpGenerator/PhpNamespace.fqn.phpt +++ b/tests/PhpGenerator/PhpNamespace.fqn.phpt @@ -9,7 +9,6 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; use Nette\PhpGenerator\PhpNamespace; use Nette\PhpGenerator\Printer; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/PhpNamespace.phpt b/tests/PhpGenerator/PhpNamespace.phpt index 1542ad04..e1fc4bb9 100644 --- a/tests/PhpGenerator/PhpNamespace.phpt +++ b/tests/PhpGenerator/PhpNamespace.phpt @@ -4,7 +4,6 @@ declare(strict_types=1); use Nette\PhpGenerator\PhpNamespace; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/PhpNamespace.print.phpt b/tests/PhpGenerator/PhpNamespace.print.phpt index bcdc3ba8..a0281710 100644 --- a/tests/PhpGenerator/PhpNamespace.print.phpt +++ b/tests/PhpGenerator/PhpNamespace.print.phpt @@ -3,7 +3,6 @@ declare(strict_types=1); use Nette\PhpGenerator\PhpNamespace; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/PhpNamespace.resolve.phpt b/tests/PhpGenerator/PhpNamespace.resolve.phpt index 50ec9ffe..08c82dee 100644 --- a/tests/PhpGenerator/PhpNamespace.resolve.phpt +++ b/tests/PhpGenerator/PhpNamespace.resolve.phpt @@ -4,7 +4,6 @@ declare(strict_types=1); use Nette\PhpGenerator\PhpNamespace; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/PhpNamespace.simplify.phpt b/tests/PhpGenerator/PhpNamespace.simplify.phpt index a093cae8..21a0494f 100644 --- a/tests/PhpGenerator/PhpNamespace.simplify.phpt +++ b/tests/PhpGenerator/PhpNamespace.simplify.phpt @@ -4,7 +4,6 @@ declare(strict_types=1); use Nette\PhpGenerator\PhpNamespace; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Printer.arrow.phpt b/tests/PhpGenerator/Printer.arrow.phpt index f7ddcccb..16328993 100644 --- a/tests/PhpGenerator/Printer.arrow.phpt +++ b/tests/PhpGenerator/Printer.arrow.phpt @@ -6,7 +6,6 @@ use Nette\PhpGenerator\Closure; use Nette\PhpGenerator\Printer; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Printer.function.phpt b/tests/PhpGenerator/Printer.function.phpt index 7cc301ab..ac1d5ff3 100644 --- a/tests/PhpGenerator/Printer.function.phpt +++ b/tests/PhpGenerator/Printer.function.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\GlobalFunction; use Nette\PhpGenerator\Printer; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Printer.namespace.phpt b/tests/PhpGenerator/Printer.namespace.phpt index 3a380100..4a69c2a4 100644 --- a/tests/PhpGenerator/Printer.namespace.phpt +++ b/tests/PhpGenerator/Printer.namespace.phpt @@ -4,7 +4,6 @@ declare(strict_types=1); use Nette\PhpGenerator\PhpNamespace; use Nette\PhpGenerator\Printer; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Printer.phpt b/tests/PhpGenerator/Printer.phpt index 97d8284e..52b034c1 100644 --- a/tests/PhpGenerator/Printer.phpt +++ b/tests/PhpGenerator/Printer.phpt @@ -7,7 +7,6 @@ use Nette\PhpGenerator\Literal; use Nette\PhpGenerator\Printer; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Printer.single.parameter.phpt b/tests/PhpGenerator/Printer.single.parameter.phpt index 0fa9fe39..b806d798 100644 --- a/tests/PhpGenerator/Printer.single.parameter.phpt +++ b/tests/PhpGenerator/Printer.single.parameter.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Literal; use Nette\PhpGenerator\Printer; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Printer.use-order.phpt b/tests/PhpGenerator/Printer.use-order.phpt index f7e642eb..24299d65 100644 --- a/tests/PhpGenerator/Printer.use-order.phpt +++ b/tests/PhpGenerator/Printer.use-order.phpt @@ -5,7 +5,6 @@ declare(strict_types=1); use Nette\PhpGenerator\PhpNamespace; use Nette\PhpGenerator\Printer; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/PsrPrinter.phpt b/tests/PhpGenerator/PsrPrinter.phpt index eb498405..a8040bb0 100644 --- a/tests/PhpGenerator/PsrPrinter.phpt +++ b/tests/PhpGenerator/PsrPrinter.phpt @@ -6,7 +6,6 @@ use Nette\PhpGenerator\ClassType; use Nette\PhpGenerator\Literal; use Nette\PhpGenerator\PsrPrinter; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/Type.phpt b/tests/PhpGenerator/Type.phpt index f889865a..4fee4013 100644 --- a/tests/PhpGenerator/Type.phpt +++ b/tests/PhpGenerator/Type.phpt @@ -4,7 +4,6 @@ declare(strict_types=1); use Nette\PhpGenerator\Type; use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; diff --git a/tests/PhpGenerator/invalidNames.phpt b/tests/PhpGenerator/invalidNames.phpt index 3a70e250..624656b5 100644 --- a/tests/PhpGenerator/invalidNames.phpt +++ b/tests/PhpGenerator/invalidNames.phpt @@ -7,7 +7,6 @@ declare(strict_types=1); use Tester\Assert; - require __DIR__ . '/../bootstrap.php'; From 688685a36d9fed43df73fe9475a52e0dff402294 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 29 Aug 2023 21:08:51 +0200 Subject: [PATCH 172/266] typo --- composer.json | 4 ++-- readme.md | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 179e5835..9e9a74bc 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.2 features.", + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.3 features.", "keywords": ["nette", "php", "code", "scaffolding"], "homepage": "https://nette.org", "license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"], @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=8.0 <8.4", + "php": "8.0 - 8.3", "nette/utils": "^3.2.9 || ^4.0" }, "require-dev": { diff --git a/readme.md b/readme.md index c3ea384d..674ced44 100644 --- a/readme.md +++ b/readme.md @@ -282,7 +282,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// or use PsrPrinter for output conforming to PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -310,7 +310,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// or use PsrPrinter for output conforming to PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -690,7 +690,7 @@ $method->addParameter('arg') echo $namespace; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// or use PsrPrinter for output conforming to PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -743,7 +743,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// or use PsrPrinter for output conforming to PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` From df22ef134ee83a0b132b37946747d1a08fe59ea8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 29 Aug 2023 19:03:41 +0200 Subject: [PATCH 173/266] Extractor::toPhp() removes doc comments [Closes #135] --- src/PhpGenerator/Extractor.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 9daeeabd..c38c9207 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -429,9 +429,11 @@ private function toVisibility(int $flags): ?string } - private function toPhp(mixed $value): string + private function toPhp(Node $value): string { - return $this->printer->prettyPrint([$value]); + $dolly = clone $value; + $dolly->setAttribute('comments', []); + return $this->printer->prettyPrint([$dolly]); } From 290bbb7d328f6942a79f95fa3e1bdd0372b7d043 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 29 Aug 2023 18:41:23 +0200 Subject: [PATCH 174/266] Parameters can have comments --- src/PhpGenerator/Parameter.php | 1 + src/PhpGenerator/Printer.php | 12 +++++------- src/PhpGenerator/PromotedParameter.php | 1 - tests/PhpGenerator/ClassType.attributes.phpt | 1 + tests/PhpGenerator/ClassType.phpt | 3 ++- .../expected/ClassType.attributes.expect | 1 + tests/PhpGenerator/expected/ClassType.expect | 7 ++++++- tests/PhpGenerator/expected/Extractor.classes.expect | 11 +++++++++-- tests/PhpGenerator/fixtures/classes.php | 2 +- 9 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/PhpGenerator/Parameter.php b/src/PhpGenerator/Parameter.php index 68397aa9..94cd6710 100644 --- a/src/PhpGenerator/Parameter.php +++ b/src/PhpGenerator/Parameter.php @@ -23,6 +23,7 @@ class Parameter use Nette\SmartObject; use Traits\NameAware; use Traits\AttributeAware; + use Traits\CommentAware; private bool $reference = false; private ?string $type = null; diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 1c5ee6bf..19087961 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -331,7 +331,7 @@ protected function printParameters(Closure|GlobalFunction|Method $function, int $special = false; foreach ($function->getParameters() as $param) { $param->validate(); - $special = $special || $param instanceof PromotedParameter || $param->getAttributes(); + $special = $special || $param instanceof PromotedParameter || $param->getAttributes() || $param->getComment(); } if (!$special || ($this->singleParameterOnOneLine && count($function->getParameters()) === 1)) { @@ -352,15 +352,13 @@ private function formatParameters(Closure|GlobalFunction|Method $function, bool foreach ($params as $param) { $variadic = $function->isVariadic() && $param === end($params); - $promoted = $param instanceof PromotedParameter ? $param : null; $attrs = $this->printAttributes($param->getAttributes(), inline: true); $res .= - ($promoted ? $this->printDocComment($promoted) : '') + $this->printDocComment($param) . ($attrs ? ($multiline ? substr($attrs, 0, -1) . "\n" : $attrs) : '') - . ($promoted ? - ($promoted->getVisibility() ?: 'public') - . ($promoted->isReadOnly() && $param->getType() ? ' readonly' : '') - . ' ' : '') + . ($param instanceof PromotedParameter + ? ($param->getVisibility() ?: 'public') . ($param->isReadOnly() && $param->getType() ? ' readonly' : '') . ' ' + : '') . ltrim($this->printType($param->getType(), $param->isNullable()) . ' ') . ($param->isReference() ? '&' : '') . ($variadic ? '...' : '') diff --git a/src/PhpGenerator/PromotedParameter.php b/src/PhpGenerator/PromotedParameter.php index 65851db2..f9ae4efe 100644 --- a/src/PhpGenerator/PromotedParameter.php +++ b/src/PhpGenerator/PromotedParameter.php @@ -18,7 +18,6 @@ final class PromotedParameter extends Parameter { use Traits\VisibilityAware; - use Traits\CommentAware; private bool $readOnly = false; diff --git a/tests/PhpGenerator/ClassType.attributes.phpt b/tests/PhpGenerator/ClassType.attributes.phpt index 1ea41b09..b6ea2a00 100644 --- a/tests/PhpGenerator/ClassType.attributes.phpt +++ b/tests/PhpGenerator/ClassType.attributes.phpt @@ -34,6 +34,7 @@ $method = $class->addMethod('getHandle') ->addAttribute('ExampleAttribute'); $method->addParameter('mode') + ->addComment('comment') ->addAttribute('ExampleAttribute') ->addAttribute('WithArguments', [123]); diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index 2c87f0ca..b012a42a 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -129,7 +129,8 @@ Assert::same($p, $method->getParameter('foo')); $method->removeParameter('foo'); Assert::false($method->hasParameter('foo')); -$method->addParameter('item'); +$method->addParameter('item') + ->addComment('comment'); $method->addParameter('res', null) ->setReference() diff --git a/tests/PhpGenerator/expected/ClassType.attributes.expect b/tests/PhpGenerator/expected/ClassType.attributes.expect index 9ed82714..46ee3389 100644 --- a/tests/PhpGenerator/expected/ClassType.attributes.expect +++ b/tests/PhpGenerator/expected/ClassType.attributes.expect @@ -21,6 +21,7 @@ class Example */ #[ExampleAttribute] public function getHandle( + /** comment */ #[ExampleAttribute, WithArguments(123)] $mode, ) { diff --git a/tests/PhpGenerator/expected/ClassType.expect b/tests/PhpGenerator/expected/ClassType.expect index ccd736b2..0c64edb0 100644 --- a/tests/PhpGenerator/expected/ClassType.expect +++ b/tests/PhpGenerator/expected/ClassType.expect @@ -48,5 +48,10 @@ abstract class Example extends ParentClass implements IExample, IOne } - abstract public function show($item, array|null &$res = null, stdClass|string|null $bar = null); + abstract public function show( + /** comment */ + $item, + array|null &$res = null, + stdClass|string|null $bar = null, + ); } diff --git a/tests/PhpGenerator/expected/Extractor.classes.expect b/tests/PhpGenerator/expected/Extractor.classes.expect index 3397988c..c37fe1f4 100644 --- a/tests/PhpGenerator/expected/Extractor.classes.expect +++ b/tests/PhpGenerator/expected/Extractor.classes.expect @@ -60,8 +60,15 @@ class Class2 extends Class1 implements Interface2 * Func3 * @return Class1 */ - private function &func3(array $a, Class2 $b, Unknown $c, \Xyz\Unknown $d, ?callable $e, $f) - { + private function &func3( + /** foo */ + array $a, + Class2 $b, + Unknown $c, + \Xyz\Unknown $d, + ?callable $e, + $f, + ) { } diff --git a/tests/PhpGenerator/fixtures/classes.php b/tests/PhpGenerator/fixtures/classes.php index ae7ba49e..3a58dba7 100644 --- a/tests/PhpGenerator/fixtures/classes.php +++ b/tests/PhpGenerator/fixtures/classes.php @@ -67,7 +67,7 @@ class Class2 extends Class1 implements Interface2 * Func3 * @return Class1 */ - private function &func3(array $a, Class2 $b, \Abc\Unknown $c, \Xyz\Unknown $d, ?callable $e, $f) + private function &func3(/** foo */array $a, Class2 $b, \Abc\Unknown $c, \Xyz\Unknown $d, ?callable $e, $f) { } From 163c5e9e8dc3cc5eedc4887b5e0198fcfb41354b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 29 Aug 2023 20:58:03 +0200 Subject: [PATCH 175/266] improved phpDoc --- src/PhpGenerator/Extractor.php | 5 ++++- src/PhpGenerator/Parameter.php | 1 + src/PhpGenerator/PhpFile.php | 2 +- src/PhpGenerator/PhpNamespace.php | 6 +++--- src/PhpGenerator/Property.php | 1 + src/PhpGenerator/Traits/FunctionLike.php | 1 + 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index c38c9207..83f65e07 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -357,7 +357,10 @@ private function addEnumCaseToClass(EnumType $class, Node\Stmt\EnumCase $node): } - private function addCommentAndAttributes($element, Node $node): void + private function addCommentAndAttributes( + PhpFile|ClassLike|Constant|Property|GlobalFunction|Method|Parameter|EnumCase|TraitUse $element, + Node $node, + ): void { if ($node->getDocComment()) { $comment = $node->getDocComment()->getReformattedText(); diff --git a/src/PhpGenerator/Parameter.php b/src/PhpGenerator/Parameter.php index 94cd6710..975d0449 100644 --- a/src/PhpGenerator/Parameter.php +++ b/src/PhpGenerator/Parameter.php @@ -52,6 +52,7 @@ public function setType(?string $type): static } + /** @return ($asObject is true ? ?Type : ?string) */ public function getType(bool $asObject = false): Type|string|null { return $asObject && $this->type diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index 68fd89bc..34142f5a 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -97,7 +97,7 @@ public function getNamespaces(): array } - /** @return ClassLike[] */ + /** @return (ClassType|InterfaceType|TraitType|EnumType)[] */ public function getClasses(): array { $classes = []; diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 0303d2dd..ef9b324e 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -50,7 +50,7 @@ final class PhpNamespace self::NameConstant => [], ]; - /** @var ClassLike[] */ + /** @var (ClassType|InterfaceType|TraitType|EnumType)[] */ private array $classes = []; /** @var GlobalFunction[] */ @@ -253,7 +253,7 @@ public function simplifyName(string $name, string $of = self::NameNormal): strin } - public function add(ClassLike $class): static + public function add(ClassType|InterfaceType|TraitType|EnumType $class): static { $name = $class->getName(); if ($name === null) { @@ -327,7 +327,7 @@ public function removeFunction(string $name): static } - /** @return ClassLike[] */ + /** @return (ClassType|InterfaceType|TraitType|EnumType)[] */ public function getClasses(): array { $res = []; diff --git a/src/PhpGenerator/Property.php b/src/PhpGenerator/Property.php index d1ffbafa..01529a0e 100644 --- a/src/PhpGenerator/Property.php +++ b/src/PhpGenerator/Property.php @@ -68,6 +68,7 @@ public function setType(?string $type): static } + /** @return ($asObject is true ? ?Type : ?string) */ public function getType(bool $asObject = false): Type|string|null { return $asObject && $this->type diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index 7a92a8db..df0a6814 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -141,6 +141,7 @@ public function setReturnType(?string $type): static } + /** @return ($asObject is true ? ?Type : ?string) */ public function getReturnType(bool $asObject = false): Type|string|null { return $asObject && $this->returnType From 72cf9e20686904678d9c1de664c6f7a8c9d676ed Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 29 Aug 2023 19:28:50 +0200 Subject: [PATCH 176/266] added Literal::new() [Closes #130] --- readme.md | 26 ++++++++++++++----- src/PhpGenerator/Literal.php | 9 +++++++ tests/PhpGenerator/ClassType.attributes.phpt | 7 ++++- tests/PhpGenerator/ClassType.promotion.phpt | 2 +- tests/PhpGenerator/Dumper.dump().phpt | 6 +++++ .../expected/ClassType.attributes.expect | 2 +- 6 files changed, 43 insertions(+), 9 deletions(-) diff --git a/readme.md b/readme.md index 674ced44..456fe406 100644 --- a/readme.md +++ b/readme.md @@ -582,6 +582,13 @@ new Literal('substr(?, ?)', [$a, $b]); // generates, for example: substr('hello', 5); ``` +The literal representing the creation of a new object is easily generated by the `new` method: + +```php +Literal::new(Demo::class, [$a, 'foo' => $b]); +// generates, for example: new Demo(10, foo: 20) +``` + Attributes ---------- @@ -590,10 +597,15 @@ You can add PHP 8 attributes to all classes, methods, properties, constants, enu ```php $class = new Nette\PhpGenerator\ClassType('Demo'); -$class->addAttribute('Deprecated'); +$class->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], +]); $class->addProperty('list') - ->addAttribute('WithArguments', [1, 2]); + ->addAttribute('Deprecated'); $method = $class->addMethod('count') ->addAttribute('Foo\Cached', ['mode' => true]); @@ -607,16 +619,18 @@ echo $class; Result: ```php -#[Deprecated] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Demo { - #[WithArguments(1, 2)] + #[Deprecated] public $list; #[Foo\Cached(mode: true)] - public function count(#[Bar] $items) - { + public function count( + #[Bar] + $items, + ) { } } ``` diff --git a/src/PhpGenerator/Literal.php b/src/PhpGenerator/Literal.php index 1d751680..d48f6a4e 100644 --- a/src/PhpGenerator/Literal.php +++ b/src/PhpGenerator/Literal.php @@ -15,6 +15,15 @@ */ class Literal { + /** + * Creates a literal representing the creation of an object using the new operator. + */ + public static function new(string $class, array $args = []): self + { + return new self('new ' . $class . '(...?:)', [$args]); + } + + public function __construct( private string $value, /** @var ?mixed[] */ diff --git a/tests/PhpGenerator/ClassType.attributes.phpt b/tests/PhpGenerator/ClassType.attributes.phpt index b6ea2a00..8ec05719 100644 --- a/tests/PhpGenerator/ClassType.attributes.phpt +++ b/tests/PhpGenerator/ClassType.attributes.phpt @@ -18,7 +18,12 @@ $class ->addComment('Description of class.') ->addAttribute('ExampleAttribute') ->addAttribute('WithArgument', [new Literal('Foo::BAR')]) - ->addAttribute('NamedArguments', ['foo' => 'bar', 'bar' => [1, 2, 3]]); + ->addAttribute('Table', [ + 'name' => 'user', + 'constraints' => [ + Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]), + ], + ]); $class->addConstant('FOO', 123) ->addComment('Commented') diff --git a/tests/PhpGenerator/ClassType.promotion.phpt b/tests/PhpGenerator/ClassType.promotion.phpt index ba4944ee..06649810 100644 --- a/tests/PhpGenerator/ClassType.promotion.phpt +++ b/tests/PhpGenerator/ClassType.promotion.phpt @@ -18,7 +18,7 @@ $method->addPromotedParameter('c') ->addComment('promo') ->addAttribute('Example'); -$method->addPromotedParameter('d', new Literal('new Draft(?)', [10])) +$method->addPromotedParameter('d', Literal::new('Draft', [10])) ->setType('Draft') ->setReadOnly(); diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index 6fb4467c..29115522 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -51,6 +51,12 @@ Assert::same("[strlen('hello')]", $dumper->dump([new Literal('strlen(?)', ['hell Assert::same("a\nb", $dumper->dump(new Literal("a\r\nb"))); +// Literal::new +Assert::same('new stdClass()', $dumper->dump(Literal::new('stdClass'))); +Assert::same('new stdClass(10, 20)', $dumper->dump(Literal::new('stdClass', [10, 20]))); +Assert::same('new stdClass(10, c: 20)', $dumper->dump(Literal::new('stdClass', [10, 'c' => 20]))); + + // arrays Assert::same('[]', $dumper->dump([])); Assert::same('[1, 2, 3]', $dumper->dump([1, 2, 3])); diff --git a/tests/PhpGenerator/expected/ClassType.attributes.expect b/tests/PhpGenerator/expected/ClassType.attributes.expect index 46ee3389..f9125620 100644 --- a/tests/PhpGenerator/expected/ClassType.attributes.expect +++ b/tests/PhpGenerator/expected/ClassType.attributes.expect @@ -3,7 +3,7 @@ */ #[ExampleAttribute] #[WithArgument(Foo::BAR)] -#[NamedArguments(foo: 'bar', bar: [1, 2, 3])] +#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])] class Example { /** Commented */ From c0b51b61757d62d4d20d54ca4c0d33c8479796ef Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 26 Sep 2023 02:31:31 +0200 Subject: [PATCH 177/266] coding style --- src/PhpGenerator/Dumper.php | 10 +++++----- src/PhpGenerator/Extractor.php | 2 +- src/PhpGenerator/PhpNamespace.php | 2 +- src/PhpGenerator/Printer.php | 4 ++-- src/PhpGenerator/TraitUse.php | 2 +- src/PhpGenerator/Type.php | 4 ++-- tests/PhpGenerator/ClassType.from.bodies.phpt | 8 +++++--- tests/PhpGenerator/Helpers.comments.phpt | 2 +- .../Helpers.isNamespaceIdentifier.phpt | 4 ++-- tests/PhpGenerator/PhpNamespace.add.phpt | 20 ++++++++++--------- tests/PhpGenerator/Type.phpt | 8 ++++---- 11 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 1ab2a608..a2dde21f 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -55,7 +55,7 @@ private function dumpVar(mixed &$var, array $parents = [], int $level = 0, int $ throw new Nette\InvalidArgumentException('Cannot dump resource.'); } else { - return var_export($var, true); + return var_export($var, return: true); } } @@ -105,7 +105,7 @@ private function dumpArray(array &$var, array $parents, int $level, int $column) if (empty($var)) { return '[]'; - } elseif ($level > $this->maxDepth || in_array($var, $parents, true)) { + } elseif ($level > $this->maxDepth || in_array($var, $parents, strict: true)) { throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.'); } @@ -140,10 +140,10 @@ private function dumpObject(object $var, array $parents, int $level): string { $class = $var::class; - if (in_array($class, [\DateTime::class, \DateTimeImmutable::class], true)) { + if (in_array($class, [\DateTime::class, \DateTimeImmutable::class], strict: true)) { return $this->format("new \\$class(?, new \\DateTimeZone(?))", $var->format('Y-m-d H:i:s.u'), $var->getTimeZone()->getName()); - } elseif ($var instanceof \Serializable) { // deprecated + } elseif ($var instanceof \Serializable) { return 'unserialize(' . $this->dumpString(serialize($var)) . ')'; } elseif ($var instanceof \UnitEnum) { @@ -162,7 +162,7 @@ private function dumpObject(object $var, array $parents, int $level): string } elseif ((new \ReflectionObject($var))->isAnonymous()) { throw new Nette\InvalidArgumentException('Cannot dump anonymous class.'); - } elseif ($level > $this->maxDepth || in_array($var, $parents, true)) { + } elseif ($level > $this->maxDepth || in_array($var, $parents, strict: true)) { throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.'); } diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 83f65e07..7ec3b3b7 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -295,7 +295,7 @@ private function addFunctionToFile(PhpFile $phpFile, Node\Stmt\Function_ $node): private function addTraitToClass(ClassLike $class, Node\Stmt\TraitUse $node): void { foreach ($node->traits as $item) { - $trait = $class->addTrait($item->toString(), true); + $trait = $class->addTrait($item->toString()); } foreach ($node->adaptations as $item) { diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index ef9b324e..8eb68c12 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -103,7 +103,7 @@ public function getBracketedSyntax(): bool public function addUse(string $name, ?string $alias = null, string $of = self::NameNormal): static { if ( - !Helpers::isNamespaceIdentifier($name, true) + !Helpers::isNamespaceIdentifier($name, allowLeadingSlash: true) || (Helpers::isIdentifier($name) && isset(Helpers::Keywords[strtolower($name)])) ) { throw new Nette\InvalidArgumentException("Value '$name' is not valid class/function/constant name."); diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 19087961..83f45d9e 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -335,13 +335,13 @@ protected function printParameters(Closure|GlobalFunction|Method $function, int } if (!$special || ($this->singleParameterOnOneLine && count($function->getParameters()) === 1)) { - $line = $this->formatParameters($function, false); + $line = $this->formatParameters($function, multiline: false); if (!str_contains($line, "\n") && strlen($line) + $column <= $this->wrapLength) { return $line; } } - return $this->formatParameters($function, true); + return $this->formatParameters($function, multiline: true); } diff --git a/src/PhpGenerator/TraitUse.php b/src/PhpGenerator/TraitUse.php index 9a9b1bfd..170b09ef 100644 --- a/src/PhpGenerator/TraitUse.php +++ b/src/PhpGenerator/TraitUse.php @@ -30,7 +30,7 @@ final class TraitUse public function __construct(string $name, ?ClassLike $parent = null) { - if (!Nette\PhpGenerator\Helpers::isNamespaceIdentifier($name, true)) { + if (!Nette\PhpGenerator\Helpers::isNamespaceIdentifier($name, allowLeadingSlash: true)) { throw new Nette\InvalidArgumentException("Value '$name' is not valid trait name."); } diff --git a/src/PhpGenerator/Type.php b/src/PhpGenerator/Type.php index 9abd87cd..da17b2fc 100644 --- a/src/PhpGenerator/Type.php +++ b/src/PhpGenerator/Type.php @@ -83,9 +83,9 @@ class Type public const STATIC = self::Static; - public static function nullable(string $type, bool $state = true): string + public static function nullable(string $type, bool $nullable = true): string { - return ($state ? '?' : '') . ltrim($type, '?'); + return ($nullable ? '?' : '') . ltrim($type, '?'); } diff --git a/tests/PhpGenerator/ClassType.from.bodies.phpt b/tests/PhpGenerator/ClassType.from.bodies.phpt index 302f305b..a98a66f0 100644 --- a/tests/PhpGenerator/ClassType.from.bodies.phpt +++ b/tests/PhpGenerator/ClassType.from.bodies.phpt @@ -9,9 +9,11 @@ require __DIR__ . '/../bootstrap.php'; require __DIR__ . '/fixtures/bodies.php'; -Assert::exception(function () { - ClassType::from(PDO::class, withBodies: true); -}, Nette\InvalidStateException::class, 'Source code of PDO not found.'); +Assert::exception( + fn() => ClassType::from(PDO::class, withBodies: true), + Nette\InvalidStateException::class, + 'Source code of PDO not found.', +); Assert::exception( diff --git a/tests/PhpGenerator/Helpers.comments.phpt b/tests/PhpGenerator/Helpers.comments.phpt index 2d4c6689..2889cd4e 100644 --- a/tests/PhpGenerator/Helpers.comments.phpt +++ b/tests/PhpGenerator/Helpers.comments.phpt @@ -15,7 +15,7 @@ require __DIR__ . '/../bootstrap.php'; Assert::same('', Helpers::formatDocComment(' ')); Assert::same("/** @var string */\n", Helpers::formatDocComment('@var string')); Assert::same("/**\n * @var string\n */\n", Helpers::formatDocComment("@var string\n")); -Assert::same("/**\n * @var string\n */\n", Helpers::formatDocComment('@var string', true)); +Assert::same("/**\n * @var string\n */\n", Helpers::formatDocComment('@var string', forceMultiLine: true)); Assert::same("/**\n * A\n * B\n * C\n */\n", Helpers::formatDocComment("A\nB\nC\n")); Assert::same("/**\n * @var string\n */\n", Helpers::formatDocComment("@var string \r\n")); Assert::same("/**\n * A\n *\n * B\n */\n", Helpers::formatDocComment("A\n\nB")); diff --git a/tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt b/tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt index f0c1febc..acdb92d6 100644 --- a/tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt +++ b/tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt @@ -17,5 +17,5 @@ Assert::false(Helpers::isNamespaceIdentifier('Item\\\\Item')); Assert::false(Helpers::isNamespaceIdentifier('\\Item')); Assert::false(Helpers::isNamespaceIdentifier('Item\\')); -Assert::true(Helpers::isNamespaceIdentifier('\\Item', true)); -Assert::false(Helpers::isNamespaceIdentifier('Item\\', true)); +Assert::true(Helpers::isNamespaceIdentifier('\\Item', allowLeadingSlash: true)); +Assert::false(Helpers::isNamespaceIdentifier('Item\\', allowLeadingSlash: true)); diff --git a/tests/PhpGenerator/PhpNamespace.add.phpt b/tests/PhpGenerator/PhpNamespace.add.phpt index 2236292f..73f93b29 100644 --- a/tests/PhpGenerator/PhpNamespace.add.phpt +++ b/tests/PhpGenerator/PhpNamespace.add.phpt @@ -8,9 +8,11 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -Assert::exception(function () { - (new PhpNamespace('Foo'))->add(new ClassType); -}, Nette\InvalidArgumentException::class, 'Class does not have a name.'); +Assert::exception( + fn() => (new PhpNamespace('Foo'))->add(new ClassType), + Nette\InvalidArgumentException::class, + 'Class does not have a name.', +); $namespace = (new PhpNamespace('Foo')) @@ -40,10 +42,10 @@ Assert::same('X', $classB->getNamespace()->getName()); // duplicity -Assert::noError(function () use ($namespace, $classA) { - $namespace->add($classA); -}); +Assert::noError(fn() => $namespace->add($classA)); -Assert::exception(function () use ($namespace) { - $namespace->add(new ClassType('a')); -}, Nette\InvalidStateException::class, "Cannot add 'a', because it already exists."); +Assert::exception( + fn() => $namespace->add(new ClassType('a')), + Nette\InvalidStateException::class, + "Cannot add 'a', because it already exists.", +); diff --git a/tests/PhpGenerator/Type.phpt b/tests/PhpGenerator/Type.phpt index 4fee4013..aa737db6 100644 --- a/tests/PhpGenerator/Type.phpt +++ b/tests/PhpGenerator/Type.phpt @@ -10,8 +10,8 @@ require __DIR__ . '/../bootstrap.php'; 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)); -Assert::same('A', Type::nullable(A::class, false)); +Assert::same('?A', Type::nullable(A::class)); +Assert::same('A', Type::nullable(A::class, nullable: false)); -Assert::same('?A', Type::nullable('?A', true)); -Assert::same('A', Type::nullable('?A', false)); +Assert::same('?A', Type::nullable('?A')); +Assert::same('A', Type::nullable('?A', nullable: false)); From 0c650006d67356e720762feb5717f59a927af73c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 29 Aug 2023 14:11:53 +0200 Subject: [PATCH 178/266] opened 4.1-dev --- composer.json | 2 +- readme.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9e9a74bc..78827f66 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ }, "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } } } diff --git a/readme.md b/readme.md index 456fe406..3ace6d2c 100644 --- a/readme.md +++ b/readme.md @@ -860,6 +860,7 @@ echo $dumper->dump($var); // prints ['a', 'b', 123] Compatibility Table ------------------- +- PhpGenerator 4.1 is compatible with PHP 8.0 to 8.3 - PhpGenerator 4.0 is compatible with PHP 8.0 to 8.3 - PhpGenerator 3.6 is compatible with PHP 7.2 to 8.2 - PhpGenerator 3.2 – 3.5 is compatible with PHP 7.1 to 8.0 From 8dd037c6cda7cab4a84e7848645ce05901af685b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 29 Aug 2023 14:18:42 +0200 Subject: [PATCH 179/266] removed deprecated stuff --- src/PhpGenerator/ClassLike.php | 14 +------- src/PhpGenerator/ClassType.php | 41 ---------------------- src/PhpGenerator/Factory.php | 4 --- src/PhpGenerator/GlobalFunction.php | 8 ----- src/PhpGenerator/Helpers.php | 24 ------------- src/PhpGenerator/Parameter.php | 16 --------- src/PhpGenerator/PhpFile.php | 8 ----- src/PhpGenerator/PhpNamespace.php | 16 --------- src/PhpGenerator/TraitUse.php | 23 ++---------- src/PhpGenerator/Traits/ConstantsAware.php | 6 +--- src/PhpGenerator/Traits/FunctionLike.php | 8 ----- src/PhpGenerator/Traits/TraitsAware.php | 15 +++----- src/PhpGenerator/Type.php | 8 ----- 13 files changed, 9 insertions(+), 182 deletions(-) diff --git a/src/PhpGenerator/ClassLike.php b/src/PhpGenerator/ClassLike.php index 6d9c1e8b..00c2181c 100644 --- a/src/PhpGenerator/ClassLike.php +++ b/src/PhpGenerator/ClassLike.php @@ -39,25 +39,13 @@ abstract class ClassLike private ?string $name; - public static function from(string|object $class, bool $withBodies = false, ?bool $materializeTraits = null): self + public static function from(string|object $class, bool $withBodies = false): self { - if ($materializeTraits !== null) { - trigger_error(__METHOD__ . '() parameter $materializeTraits has been removed (is always false).', E_USER_DEPRECATED); - } return (new Factory) ->fromClassReflection(new \ReflectionClass($class), $withBodies); } - /** @deprecated use from(..., withBodies: true) */ - public static function withBodiesFrom(string|object $class): self - { - trigger_error(__METHOD__ . '() is deprecated, use from(..., withBodies: true)', E_USER_DEPRECATED); - return (new Factory) - ->fromClassReflection(new \ReflectionClass($class), withBodies: true); - } - - public static function fromCode(string $code): self { return (new Factory) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 1877d163..f4ca3cc2 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -81,65 +81,24 @@ public function __construct(?string $name = null, ?PhpNamespace $namespace = nul } - /** @deprecated */ - public function setClass(): static - { - trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED); - $this->type = self::TYPE_CLASS; - return $this; - } - - public function isClass(): bool { return $this->type === self::TYPE_CLASS; } - /** @deprecated create object using 'new Nette\PhpGenerator\InterfaceType' */ - public function setInterface(): static - { - trigger_error(__METHOD__ . "() is deprecated, create object using 'new Nette\\PhpGenerator\\InterfaceType'", E_USER_DEPRECATED); - $this->type = self::TYPE_INTERFACE; - return $this; - } - - public function isInterface(): bool { return $this->type === self::TYPE_INTERFACE; } - /** @deprecated create object using 'new Nette\PhpGenerator\TraitType' */ - public function setTrait(): static - { - trigger_error(__METHOD__ . "() is deprecated, create object using 'new Nette\\PhpGenerator\\TraitType'", E_USER_DEPRECATED); - $this->type = self::TYPE_TRAIT; - return $this; - } - - public function isTrait(): bool { return $this->type === self::TYPE_TRAIT; } - /** @deprecated create object using 'new Nette\PhpGenerator\InterfaceType' or 'TraitType' */ - public function setType(string $type): static - { - $upper = ucfirst($type); - trigger_error(__METHOD__ . "() is deprecated, create object using 'new Nette\\PhpGenerator\\{$upper}Type'", E_USER_DEPRECATED); - if (!in_array($type, [self::TYPE_CLASS, self::TYPE_INTERFACE, self::TYPE_TRAIT], true)) { - throw new Nette\InvalidArgumentException('Argument must be class|interface|trait.'); - } - - $this->type = $type; - return $this; - } - - /** @deprecated */ public function getType(): string { diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 61b24dca..3e91c0d6 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -31,12 +31,8 @@ final class Factory public function fromClassReflection( \ReflectionClass $from, bool $withBodies = false, - ?bool $materializeTraits = null, ): ClassLike { - if ($materializeTraits !== null) { - trigger_error(__METHOD__ . '() parameter $materializeTraits has been removed (is always false).', E_USER_DEPRECATED); - } if ($withBodies && $from->isAnonymous()) { throw new Nette\NotSupportedException('The $withBodies parameter cannot be used for anonymous functions.'); } diff --git a/src/PhpGenerator/GlobalFunction.php b/src/PhpGenerator/GlobalFunction.php index ae1e4f88..8c0b563e 100644 --- a/src/PhpGenerator/GlobalFunction.php +++ b/src/PhpGenerator/GlobalFunction.php @@ -31,14 +31,6 @@ public static function from(string $function, bool $withBody = false): self } - /** @deprecated use GlobalFunction::from(..., withBody: true) */ - public static function withBodyFrom(string $function): self - { - trigger_error(__METHOD__ . '() is deprecated, use GlobalFunction::from(..., withBody: true)', E_USER_DEPRECATED); - return (new Factory)->fromFunctionReflection(new \ReflectionFunction($function), withBody: true); - } - - public function __toString(): string { return (new Printer)->printFunction($this); diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index fc3e1c3f..7290d390 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -50,30 +50,6 @@ final class Helpers KEYWORDS = self::Keywords; - /** @deprecated use (new Nette\PhpGenerator\Dumper)->dump() */ - public static function dump(mixed $var): string - { - trigger_error(__METHOD__ . '() is deprecated, use (new Nette\PhpGenerator\Dumper)->dump().', E_USER_DEPRECATED); - return (new Dumper)->dump($var); - } - - - /** @deprecated use (new Nette\PhpGenerator\Dumper)->format() */ - public static function format(string $statement, mixed ...$args): string - { - trigger_error(__METHOD__ . '() is deprecated, use (new Nette\PhpGenerator\Dumper)->format().', E_USER_DEPRECATED); - return (new Dumper)->format($statement, ...$args); - } - - - /** @deprecated use (new Nette\PhpGenerator\Dumper)->format() */ - public static function formatArgs(string $statement, array $args): string - { - trigger_error(__METHOD__ . '() is deprecated, use (new Nette\PhpGenerator\Dumper)->format().', E_USER_DEPRECATED); - return (new Dumper)->format($statement, ...$args); - } - - public static function formatDocComment(string $content, bool $forceMultiLine = false): string { $s = trim($content); diff --git a/src/PhpGenerator/Parameter.php b/src/PhpGenerator/Parameter.php index 975d0449..ba549365 100644 --- a/src/PhpGenerator/Parameter.php +++ b/src/PhpGenerator/Parameter.php @@ -61,22 +61,6 @@ public function getType(bool $asObject = false): Type|string|null } - /** @deprecated use setType() */ - public function setTypeHint(?string $type): static - { - trigger_error(__METHOD__ . '() is deprecated, use setType().', E_USER_DEPRECATED); - return $this->setType($type); - } - - - /** @deprecated use getType() */ - public function getTypeHint(): ?string - { - trigger_error(__METHOD__ . '() is deprecated, use getType().', E_USER_DEPRECATED); - return $this->getType(); - } - - public function setNullable(bool $state = true): static { $this->nullable = $state; diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index 34142f5a..37e89e49 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -150,14 +150,6 @@ public function hasStrictTypes(): bool } - /** @deprecated use hasStrictTypes() */ - public function getStrictTypes(): bool - { - trigger_error(__METHOD__ . '() is deprecated, use hasStrictTypes().', E_USER_DEPRECATED); - return $this->strictTypes; - } - - public function __toString(): string { return (new Printer)->printFile($this); diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 8eb68c12..3eea517d 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -89,14 +89,6 @@ public function hasBracketedSyntax(): bool } - /** @deprecated use hasBracketedSyntax() */ - public function getBracketedSyntax(): bool - { - trigger_error(__METHOD__ . '() is deprecated, use hasBracketedSyntax().', E_USER_DEPRECATED); - return $this->bracketedSyntax; - } - - /** * @throws InvalidStateException */ @@ -174,14 +166,6 @@ public function getUses(string $of = self::NameNormal): array } - /** @deprecated use simplifyName() */ - public function unresolveName(string $name): string - { - trigger_error(__METHOD__ . '() is deprecated, use simplifyName()', E_USER_DEPRECATED); - return $this->simplifyName($name); - } - - public function resolveName(string $name, string $of = self::NameNormal): string { if (isset(Helpers::Keywords[strtolower($name)]) || $name === '') { diff --git a/src/PhpGenerator/TraitUse.php b/src/PhpGenerator/TraitUse.php index 170b09ef..f72c4349 100644 --- a/src/PhpGenerator/TraitUse.php +++ b/src/PhpGenerator/TraitUse.php @@ -17,25 +17,21 @@ */ final class TraitUse { - use Nette\SmartObject { - __call as private parentCall; - } + use Nette\SmartObject; use Traits\NameAware; use Traits\CommentAware; /** @var string[] */ private array $resolutions = []; - private ?ClassLike $parent; - public function __construct(string $name, ?ClassLike $parent = null) + public function __construct(string $name) { if (!Nette\PhpGenerator\Helpers::isNamespaceIdentifier($name, allowLeadingSlash: true)) { throw new Nette\InvalidArgumentException("Value '$name' is not valid trait name."); } $this->name = $name; - $this->parent = $parent; } @@ -51,19 +47,4 @@ public function getResolutions(): array { return $this->resolutions; } - - - /** @param mixed[] $args */ - public function __call(string $nm, array $args): mixed - { - if (!$this->parent) { - return $this->parentCall($nm, $args); - } - $trace = debug_backtrace(0); - $loc = isset($trace[0]['file']) - ? ' in ' . $trace[0]['file'] . ':' . $trace[0]['line'] - : ''; - trigger_error('The ClassType::addTrait() method now returns a TraitUse object instead of ClassType. Please fix the method chaining' . $loc, E_USER_DEPRECATED); - return $this->parent->$nm(...$args); - } } diff --git a/src/PhpGenerator/Traits/ConstantsAware.php b/src/PhpGenerator/Traits/ConstantsAware.php index 12968fc7..eab1ae9a 100644 --- a/src/PhpGenerator/Traits/ConstantsAware.php +++ b/src/PhpGenerator/Traits/ConstantsAware.php @@ -25,13 +25,9 @@ trait ConstantsAware /** @param Constant[] $consts */ public function setConstants(array $consts): static { + (function (Constant ...$consts) {})(...$consts); $this->consts = []; foreach ($consts as $k => $const) { - if (!$const instanceof Constant) { - trigger_error(__METHOD__ . '() accepts an array of Constant as parameter, ' . get_debug_type($const) . ' given.', E_USER_DEPRECATED); - $const = (new Constant($k))->setValue($const)->setPublic(); - } - $this->consts[$const->getName()] = $const; } diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index df0a6814..05e039f8 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -174,12 +174,4 @@ public function isReturnNullable(): bool { return $this->returnNullable; } - - - /** @deprecated use isReturnNullable() */ - public function getReturnNullable(): bool - { - trigger_error(__METHOD__ . '() is deprecated, use isReturnNullable().', E_USER_DEPRECATED); - return $this->returnNullable; - } } diff --git a/src/PhpGenerator/Traits/TraitsAware.php b/src/PhpGenerator/Traits/TraitsAware.php index 0d549784..25d0cd21 100644 --- a/src/PhpGenerator/Traits/TraitsAware.php +++ b/src/PhpGenerator/Traits/TraitsAware.php @@ -25,14 +25,9 @@ trait TraitsAware /** @param TraitUse[] $traits */ public function setTraits(array $traits): static { - (function (TraitUse|string ...$traits) {})(...$traits); + (function (TraitUse ...$traits) {})(...$traits); $this->traits = []; foreach ($traits as $trait) { - if (!$trait instanceof TraitUse) { - trigger_error(__METHOD__ . '() accepts an array of TraitUse as parameter, string given.', E_USER_DEPRECATED); - $trait = new TraitUse($trait); - } - $this->traits[$trait->getName()] = $trait; } @@ -47,14 +42,14 @@ public function getTraits(): array } - public function addTrait(string $name, array|bool|null $deprecatedParam = null): TraitUse + public function addTrait(string $name): TraitUse { if (isset($this->traits[$name])) { throw new Nette\InvalidStateException("Cannot add trait '$name', because it already exists."); } - $this->traits[$name] = $trait = new TraitUse($name, $this); - if (is_array($deprecatedParam)) { - array_map(fn($item) => $trait->addResolution($item), $deprecatedParam); + $this->traits[$name] = $trait = new TraitUse($name); + if (func_num_args() > 1 && is_array(func_get_arg(1))) { // back compatibility + array_map(fn($item) => $trait->addResolution($item), func_get_arg(1)); } return $trait; diff --git a/src/PhpGenerator/Type.php b/src/PhpGenerator/Type.php index da17b2fc..d122ab71 100644 --- a/src/PhpGenerator/Type.php +++ b/src/PhpGenerator/Type.php @@ -99,12 +99,4 @@ public static function intersection(string ...$types): string { return implode('&', $types); } - - - /** @deprecated use get_debug_type() */ - public static function getType(mixed $value): ?string - { - trigger_error(__METHOD__ . '() is deprecated, use PHP function get_debug_type()', E_USER_DEPRECATED); - return is_resource($value) ? null : get_debug_type($value); - } } From 7326d9776d2007f9dcbf4a276f829c111ecc922a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 29 Aug 2023 14:11:20 +0200 Subject: [PATCH 180/266] removed Nette\SmartObject --- src/PhpGenerator/Attribute.php | 2 -- src/PhpGenerator/ClassLike.php | 1 - src/PhpGenerator/ClassType.php | 3 --- src/PhpGenerator/Closure.php | 5 ----- src/PhpGenerator/Constant.php | 3 --- src/PhpGenerator/EnumCase.php | 3 --- src/PhpGenerator/Extractor.php | 2 -- src/PhpGenerator/Factory.php | 2 -- src/PhpGenerator/GlobalFunction.php | 5 ----- src/PhpGenerator/InterfaceType.php | 2 -- src/PhpGenerator/Method.php | 3 --- src/PhpGenerator/Parameter.php | 4 ---- src/PhpGenerator/PhpFile.php | 3 --- src/PhpGenerator/PhpNamespace.php | 2 -- src/PhpGenerator/Printer.php | 2 -- src/PhpGenerator/Property.php | 3 --- src/PhpGenerator/TraitType.php | 3 --- src/PhpGenerator/TraitUse.php | 1 - 18 files changed, 49 deletions(-) diff --git a/src/PhpGenerator/Attribute.php b/src/PhpGenerator/Attribute.php index bd112c5e..4da227b4 100644 --- a/src/PhpGenerator/Attribute.php +++ b/src/PhpGenerator/Attribute.php @@ -17,8 +17,6 @@ */ final class Attribute { - use Nette\SmartObject; - private string $name; /** @var mixed[] */ diff --git a/src/PhpGenerator/ClassLike.php b/src/PhpGenerator/ClassLike.php index 00c2181c..ce49782d 100644 --- a/src/PhpGenerator/ClassLike.php +++ b/src/PhpGenerator/ClassLike.php @@ -17,7 +17,6 @@ */ abstract class ClassLike { - use Nette\SmartObject; use Traits\CommentAware; use Traits\AttributeAware; diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index f4ca3cc2..1e5ebd69 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -14,9 +14,6 @@ /** * Class description. - * - * @property-deprecated Method[] $methods - * @property-deprecated Property[] $properties */ final class ClassType extends ClassLike { diff --git a/src/PhpGenerator/Closure.php b/src/PhpGenerator/Closure.php index 398e1491..4f1fa154 100644 --- a/src/PhpGenerator/Closure.php +++ b/src/PhpGenerator/Closure.php @@ -9,17 +9,12 @@ namespace Nette\PhpGenerator; -use Nette; - /** * Closure. - * - * @property-deprecated string $body */ final class Closure { - use Nette\SmartObject; use Traits\FunctionLike; use Traits\AttributeAware; diff --git a/src/PhpGenerator/Constant.php b/src/PhpGenerator/Constant.php index 8097dba5..44c1938c 100644 --- a/src/PhpGenerator/Constant.php +++ b/src/PhpGenerator/Constant.php @@ -9,15 +9,12 @@ namespace Nette\PhpGenerator; -use Nette; - /** * Class constant. */ final class Constant { - use Nette\SmartObject; use Traits\NameAware; use Traits\VisibilityAware; use Traits\CommentAware; diff --git a/src/PhpGenerator/EnumCase.php b/src/PhpGenerator/EnumCase.php index 226fd090..36bfd2fc 100644 --- a/src/PhpGenerator/EnumCase.php +++ b/src/PhpGenerator/EnumCase.php @@ -9,15 +9,12 @@ namespace Nette\PhpGenerator; -use Nette; - /** * Enum case. */ final class EnumCase { - use Nette\SmartObject; use Traits\NameAware; use Traits\CommentAware; use Traits\AttributeAware; diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 7ec3b3b7..51b3419d 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -22,8 +22,6 @@ */ final class Extractor { - use Nette\SmartObject; - private string $code; /** @var Node[] */ diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 3e91c0d6..5cd58cc4 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -18,8 +18,6 @@ */ final class Factory { - use Nette\SmartObject; - /** @var string[][] */ private array $bodyCache = []; diff --git a/src/PhpGenerator/GlobalFunction.php b/src/PhpGenerator/GlobalFunction.php index 8c0b563e..3a6a5a3b 100644 --- a/src/PhpGenerator/GlobalFunction.php +++ b/src/PhpGenerator/GlobalFunction.php @@ -9,17 +9,12 @@ namespace Nette\PhpGenerator; -use Nette; - /** * Global function. - * - * @property-deprecated string $body */ final class GlobalFunction { - use Nette\SmartObject; use Traits\FunctionLike; use Traits\NameAware; use Traits\CommentAware; diff --git a/src/PhpGenerator/InterfaceType.php b/src/PhpGenerator/InterfaceType.php index 5a2d61d3..86a7dd03 100644 --- a/src/PhpGenerator/InterfaceType.php +++ b/src/PhpGenerator/InterfaceType.php @@ -14,8 +14,6 @@ /** * Interface description. - * - * @property-deprecated Method[] $methods */ final class InterfaceType extends ClassLike { diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index 0596658c..5c10fec4 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -14,12 +14,9 @@ /** * Class method. - * - * @property-deprecated string|null $body */ final class Method { - use Nette\SmartObject; use Traits\FunctionLike; use Traits\NameAware; use Traits\VisibilityAware; diff --git a/src/PhpGenerator/Parameter.php b/src/PhpGenerator/Parameter.php index ba549365..b40d5e00 100644 --- a/src/PhpGenerator/Parameter.php +++ b/src/PhpGenerator/Parameter.php @@ -9,18 +9,14 @@ namespace Nette\PhpGenerator; -use Nette; use Nette\Utils\Type; /** * Function/Method parameter description. - * - * @property-deprecated mixed $defaultValue */ class Parameter { - use Nette\SmartObject; use Traits\NameAware; use Traits\AttributeAware; use Traits\CommentAware; diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index 37e89e49..e8f0bc9b 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -9,8 +9,6 @@ namespace Nette\PhpGenerator; -use Nette; - /** * Instance of PHP file. @@ -22,7 +20,6 @@ */ final class PhpFile { - use Nette\SmartObject; use Traits\CommentAware; /** @var PhpNamespace[] */ diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 3eea517d..b930e8b1 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -23,8 +23,6 @@ */ final class PhpNamespace { - use Nette\SmartObject; - public const NameNormal = 'n', NameFunction = 'f', diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 83f45d9e..40485a57 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -18,8 +18,6 @@ */ class Printer { - use Nette\SmartObject; - public int $wrapLength = 120; public string $indentation = "\t"; public int $linesBetweenProperties = 0; diff --git a/src/PhpGenerator/Property.php b/src/PhpGenerator/Property.php index 01529a0e..17a42482 100644 --- a/src/PhpGenerator/Property.php +++ b/src/PhpGenerator/Property.php @@ -15,12 +15,9 @@ /** * Class property description. - * - * @property-deprecated mixed $value */ final class Property { - use Nette\SmartObject; use Traits\NameAware; use Traits\VisibilityAware; use Traits\CommentAware; diff --git a/src/PhpGenerator/TraitType.php b/src/PhpGenerator/TraitType.php index 57621ad4..43f2de30 100644 --- a/src/PhpGenerator/TraitType.php +++ b/src/PhpGenerator/TraitType.php @@ -14,9 +14,6 @@ /** * Trait description. - * - * @property-deprecated Method[] $methods - * @property-deprecated Property[] $properties */ final class TraitType extends ClassLike { diff --git a/src/PhpGenerator/TraitUse.php b/src/PhpGenerator/TraitUse.php index f72c4349..dc680e0f 100644 --- a/src/PhpGenerator/TraitUse.php +++ b/src/PhpGenerator/TraitUse.php @@ -17,7 +17,6 @@ */ final class TraitUse { - use Nette\SmartObject; use Traits\NameAware; use Traits\CommentAware; From 8b728c622c49b196513c0f95508f2f66342d1e8f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 29 Aug 2023 20:41:31 +0200 Subject: [PATCH 181/266] ClassType::class(), interface(), traits() & enum() are deprecated --- src/PhpGenerator/ClassType.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 1e5ebd69..65d25339 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -42,6 +42,7 @@ final class ClassType extends ClassLike /** @deprecated create object using 'new Nette\PhpGenerator\ClassType' */ public static function class(?string $name): self { + trigger_error(__METHOD__ . "() is deprecated, create object using 'new Nette\\PhpGenerator\\ClassType", E_USER_DEPRECATED); return new self($name); } @@ -49,6 +50,7 @@ public static function class(?string $name): self /** @deprecated create object using 'new Nette\PhpGenerator\InterfaceType' */ public static function interface(string $name): InterfaceType { + trigger_error(__METHOD__ . "() is deprecated, create object using 'new Nette\\PhpGenerator\\InterfaceType'", E_USER_DEPRECATED); return new InterfaceType($name); } @@ -56,6 +58,7 @@ public static function interface(string $name): InterfaceType /** @deprecated create object using 'new Nette\PhpGenerator\TraitType' */ public static function trait(string $name): TraitType { + trigger_error(__METHOD__ . "() is deprecated, create object using 'new Nette\\PhpGenerator\\TraitType'", E_USER_DEPRECATED); return new TraitType($name); } @@ -63,6 +66,7 @@ public static function trait(string $name): TraitType /** @deprecated create object using 'new Nette\PhpGenerator\EnumType' */ public static function enum(string $name): EnumType { + trigger_error(__METHOD__ . "() is deprecated, create object using 'new Nette\\PhpGenerator\\EnumType'", E_USER_DEPRECATED); return new EnumType($name); } From 225c3ec6da4b657ef4ea17c85823b72c5a487a08 Mon Sep 17 00:00:00 2001 From: Moritz Bischof <59830009+19bischof@users.noreply.github.com> Date: Fri, 29 Sep 2023 12:20:31 +0200 Subject: [PATCH 182/266] Printer: fixed falsy Attribute aren't printed (#142) --- src/PhpGenerator/Printer.php | 2 +- tests/PhpGenerator/ClassType.attributes.phpt | 2 +- tests/PhpGenerator/expected/ClassType.attributes.expect | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 40485a57..b0d777a0 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -422,7 +422,7 @@ protected function printAttributes(array $attrs, bool $inline = false): string foreach ($attrs as $attr) { $args = $this->dumper->format('...?:', $attr->getArguments()); $args = Helpers::simplifyTaggedNames($args, $this->namespace); - $items[] = $this->printType($attr->getName(), nullable: false) . ($args ? "($args)" : ''); + $items[] = $this->printType($attr->getName(), nullable: false) . ($args === '' ? '' : "($args)"); $inline = $inline && !str_contains($args, "\n"); } diff --git a/tests/PhpGenerator/ClassType.attributes.phpt b/tests/PhpGenerator/ClassType.attributes.phpt index 8ec05719..c2dc1c76 100644 --- a/tests/PhpGenerator/ClassType.attributes.phpt +++ b/tests/PhpGenerator/ClassType.attributes.phpt @@ -41,6 +41,6 @@ $method = $class->addMethod('getHandle') $method->addParameter('mode') ->addComment('comment') ->addAttribute('ExampleAttribute') - ->addAttribute('WithArguments', [123]); + ->addAttribute('WithArguments', [0]); sameFile(__DIR__ . '/expected/ClassType.attributes.expect', (string) $class); diff --git a/tests/PhpGenerator/expected/ClassType.attributes.expect b/tests/PhpGenerator/expected/ClassType.attributes.expect index f9125620..434918ec 100644 --- a/tests/PhpGenerator/expected/ClassType.attributes.expect +++ b/tests/PhpGenerator/expected/ClassType.attributes.expect @@ -22,7 +22,7 @@ class Example #[ExampleAttribute] public function getHandle( /** comment */ - #[ExampleAttribute, WithArguments(123)] + #[ExampleAttribute, WithArguments(0)] $mode, ) { } From bcb74f30affe6766adaeb363fb66cfeaad818238 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 14 Oct 2023 17:18:10 +0200 Subject: [PATCH 183/266] Printer: added option $omitEmptyNamespaces --- readme.md | 2 ++ src/PhpGenerator/Printer.php | 5 +++++ tests/PhpGenerator/Printer.use-order.phpt | 1 + 3 files changed, 8 insertions(+) diff --git a/readme.md b/readme.md index 3ace6d2c..33ba4332 100644 --- a/readme.md +++ b/readme.md @@ -515,6 +515,8 @@ class MyPrinter extends Nette\PhpGenerator\Printer public bool $bracesOnNextLine = true; // place one parameter in one line, even if it has an attribute or is promoted public bool $singleParameterOnOneLine = false; + // omits namespaces that do not contain any class or function + public bool $omitEmptyNamespaces = true; // separator between the right parenthesis and return type of functions and methods public string $returnTypeColon = ': '; } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index b0d777a0..2bcdf51d 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -26,6 +26,7 @@ class Printer public string $returnTypeColon = ': '; public bool $bracesOnNextLine = true; public bool $singleParameterOnOneLine = false; + public bool $omitEmptyNamespaces = true; protected ?PhpNamespace $namespace = null; protected ?Dumper $dumper; private bool $resolveTypes = true; @@ -276,6 +277,10 @@ public function printNamespace(PhpNamespace $namespace): string $items[] = $this->printFunction($function, $namespace); } + if (!$items && $this->omitEmptyNamespaces) { + return ''; + } + $body = ($uses ? $uses . "\n" : '') . implode("\n", $items); diff --git a/tests/PhpGenerator/Printer.use-order.phpt b/tests/PhpGenerator/Printer.use-order.phpt index 24299d65..009eb310 100644 --- a/tests/PhpGenerator/Printer.use-order.phpt +++ b/tests/PhpGenerator/Printer.use-order.phpt @@ -9,6 +9,7 @@ require __DIR__ . '/../bootstrap.php'; $printer = new Printer; +$printer->omitEmptyNamespaces = false; $namespace = new PhpNamespace('Foo'); $namespace->addUse('Example\Foo\EmailAlias\Bar'); $namespace->addUse('Example\Foo\Email\Test'); From 447dd6bc537a2df3e7680f7a2c8251ef8413ca3a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 15 Oct 2023 01:59:16 +0200 Subject: [PATCH 184/266] added ConstantsAware::getConstant() --- src/PhpGenerator/Traits/ConstantsAware.php | 6 ++++++ tests/PhpGenerator/ClassType.phpt | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/PhpGenerator/Traits/ConstantsAware.php b/src/PhpGenerator/Traits/ConstantsAware.php index eab1ae9a..6cf1e2df 100644 --- a/src/PhpGenerator/Traits/ConstantsAware.php +++ b/src/PhpGenerator/Traits/ConstantsAware.php @@ -42,6 +42,12 @@ public function getConstants(): array } + public function getConstant(string $name): Constant + { + return $this->consts[$name] ?? throw new Nette\InvalidArgumentException("Constant '$name' not found."); + } + + public function addConstant(string $name, mixed $value): Constant { if (isset($this->consts[$name])) { diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index b012a42a..8bf42361 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -48,7 +48,7 @@ Assert::true($class->isAbstract()); Assert::same('ParentClass', $class->getExtends()); Assert::same(['ObjectTrait' => $trait1, 'AnotherTrait' => $trait2], $class->getTraits()); Assert::count(2, $class->getConstants()); -Assert::type(Nette\PhpGenerator\Constant::class, $class->getConstants()['ROLE']); +Assert::type(Nette\PhpGenerator\Constant::class, $class->getConstant('ROLE')); $class->addConstant('FORCE_ARRAY', new Literal('Nette\Utils\Json::FORCE_ARRAY')) ->setVisibility('private') From 1962e53fd67e905292e02a1e932ca1cb1d164390 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 29 Sep 2023 11:26:56 +0200 Subject: [PATCH 185/266] ClassType: added inheritMethod() & inheritProperty() --- src/PhpGenerator/ClassType.php | 53 ++++++++++++++++++++++++ tests/PhpGenerator/Method.inherit.phpt | 51 +++++++++++++++++++++++ tests/PhpGenerator/Property.inherit.phpt | 49 ++++++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 tests/PhpGenerator/Method.inherit.phpt create mode 100644 tests/PhpGenerator/Property.inherit.phpt diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 65d25339..5c253c87 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -212,6 +212,59 @@ public function addMember(Method|Property|Constant|TraitUse $member): static } + /** + * Inherits property from parent class. + */ + public function inheritProperty(string $name, bool $returnIfExists = false): Property + { + if (isset($this->properties[$name])) { + return $returnIfExists + ? $this->properties[$name] + : throw new Nette\InvalidStateException("Cannot inherit property '$name', because it already exists."); + + } elseif (!$this->extends) { + throw new Nette\InvalidStateException("Class '{$this->getName()}' has not setExtends() set."); + } + + try { + $rp = new \ReflectionProperty($this->extends, $name); + } catch (\ReflectionException) { + throw new Nette\InvalidStateException("Property '$name' has not been found in ancestor {$this->extends}"); + } + + return $this->properties[$name] = (new Factory)->fromPropertyReflection($rp); + } + + + /** + * Inherits method from parent class or interface. + */ + public function inheritMethod(string $name, bool $returnIfExists = false): Method + { + $lower = strtolower($name); + $parents = [...(array) $this->extends, ...$this->implements]; + if (isset($this->methods[$lower])) { + return $returnIfExists + ? $this->methods[$lower] + : throw new Nette\InvalidStateException("Cannot inherit method '$name', because it already exists."); + + } elseif (!$parents) { + throw new Nette\InvalidStateException("Class '{$this->getName()}' has neither setExtends() nor setImplements() set."); + } + + foreach ($parents as $parent) { + try { + $rm = new \ReflectionMethod($parent, $name); + } catch (\ReflectionException) { + continue; + } + return $this->methods[$lower] = (new Factory)->fromMethodReflection($rm); + } + + throw new Nette\InvalidStateException("Method '$name' has not been found in any ancestor: " . implode(', ', $parents)); + } + + /** @throws Nette\InvalidStateException */ public function validate(): void { diff --git a/tests/PhpGenerator/Method.inherit.phpt b/tests/PhpGenerator/Method.inherit.phpt new file mode 100644 index 00000000..6fcc0523 --- /dev/null +++ b/tests/PhpGenerator/Method.inherit.phpt @@ -0,0 +1,51 @@ + $class->inheritMethod('bar'), + Nette\InvalidStateException::class, + "Class 'Test' has neither setExtends() nor setImplements() set.", +); + +$class->setExtends('Unknown1'); +$class->addImplement('Unknown2'); +Assert::exception( + fn() => $class->inheritMethod('bar'), + Nette\InvalidStateException::class, + "Method 'bar' has not been found in any ancestor: Unknown1, Unknown2", +); + + +// implement method +$class = new Nette\PhpGenerator\ClassType('Test'); +$class->setExtends(Foo::class); +$method = $class->inheritMethod('bar'); +Assert::match(<<<'XX' + public function bar(int $a, ...$b): void + { + } + + XX, (string) $method); + +Assert::same($method, $class->inheritMethod('bar', returnIfExists: true)); +Assert::exception( + fn() => $class->inheritMethod('bar', returnIfExists: false), + Nette\InvalidStateException::class, + "Cannot inherit method 'bar', because it already exists.", +); diff --git a/tests/PhpGenerator/Property.inherit.phpt b/tests/PhpGenerator/Property.inherit.phpt new file mode 100644 index 00000000..e110dae8 --- /dev/null +++ b/tests/PhpGenerator/Property.inherit.phpt @@ -0,0 +1,49 @@ + $class->inheritProperty('bar'), + Nette\InvalidStateException::class, + "Class 'Test' has not setExtends() set.", +); + +$class->setExtends('Unknown'); +Assert::exception( + fn() => $class->inheritProperty('bar'), + Nette\InvalidStateException::class, + "Property 'bar' has not been found in ancestor Unknown", +); + + +// implement property +$class = new Nette\PhpGenerator\ClassType('Test'); +$class->setExtends(Foo::class); +$prop = $class->inheritProperty('bar'); +Assert::match(<<<'XX' + class Test extends Foo + { + public array $bar = [123]; + } + + XX, (string) $class); + +Assert::same($prop, $class->inheritProperty('bar', returnIfExists: true)); +Assert::exception( + fn() => $class->inheritProperty('bar', returnIfExists: false), + Nette\InvalidStateException::class, + "Cannot inherit property 'bar', because it already exists.", +); From 047e8a63c4b871f27d1a88ea5decbcc425450079 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 13 Oct 2023 20:32:11 +0200 Subject: [PATCH 186/266] ClassType: getType() is deprecated --- src/PhpGenerator/ClassType.php | 22 ++-------------------- src/PhpGenerator/Printer.php | 2 +- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 5c253c87..2c2dfc0d 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -29,7 +29,6 @@ final class ClassType extends ClassLike TYPE_TRAIT = 'trait', TYPE_ENUM = 'enum'; - private string $type = self::TYPE_CLASS; private bool $final = false; private bool $abstract = false; private ?string $extends = null; @@ -82,28 +81,11 @@ public function __construct(?string $name = null, ?PhpNamespace $namespace = nul } - public function isClass(): bool - { - return $this->type === self::TYPE_CLASS; - } - - - public function isInterface(): bool - { - return $this->type === self::TYPE_INTERFACE; - } - - - public function isTrait(): bool - { - return $this->type === self::TYPE_TRAIT; - } - - /** @deprecated */ public function getType(): string { - return $this->type; + trigger_error(__METHOD__ . "() is deprecated, method always returns 'class'", E_USER_DEPRECATED); + return self::TYPE_CLASS; } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 2bcdf51d..32bb8aaf 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -234,7 +234,7 @@ public function printClass( } $line[] = match (true) { - $class instanceof ClassType => $class->getName() ? $class->getType() . ' ' . $class->getName() : null, + $class instanceof ClassType => $class->getName() ? 'class ' . $class->getName() : null, $class instanceof InterfaceType => 'interface ' . $class->getName(), $class instanceof TraitType => 'trait ' . $class->getName(), $class instanceof EnumType => 'enum ' . $class->getName() . ($enumType ? $this->returnTypeColon . $enumType : ''), From 3e1f6baa3f81fcd692db8b7c9528cf9399078e7b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 13 Oct 2023 21:47:36 +0200 Subject: [PATCH 187/266] Factory: $withBodies cannot be used for internal classes/functions --- src/PhpGenerator/Factory.php | 8 ++++---- tests/PhpGenerator/ClassType.from.bodies.phpt | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 5cd58cc4..1e34ad8c 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -31,8 +31,8 @@ public function fromClassReflection( bool $withBodies = false, ): ClassLike { - if ($withBodies && $from->isAnonymous()) { - throw new Nette\NotSupportedException('The $withBodies parameter cannot be used for anonymous functions.'); + if ($withBodies && ($from->isAnonymous() || $from->isInternal())) { + throw new Nette\NotSupportedException('The $withBodies parameter cannot be used for anonymous or internal classes.'); } $enumIface = null; @@ -177,8 +177,8 @@ public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody $function->setReturnType((string) $from->getReturnType()); if ($withBody) { - if ($from->isClosure()) { - throw new Nette\NotSupportedException('The $withBody parameter cannot be used for closures.'); + if ($from->isClosure() || $from->isInternal()) { + throw new Nette\NotSupportedException('The $withBody parameter cannot be used for closures or internal functions.'); } $function->setBody($this->getExtractor($from)->extractFunctionBody($from->name)); diff --git a/tests/PhpGenerator/ClassType.from.bodies.phpt b/tests/PhpGenerator/ClassType.from.bodies.phpt index a98a66f0..d0c667e8 100644 --- a/tests/PhpGenerator/ClassType.from.bodies.phpt +++ b/tests/PhpGenerator/ClassType.from.bodies.phpt @@ -11,8 +11,8 @@ require __DIR__ . '/fixtures/bodies.php'; Assert::exception( fn() => ClassType::from(PDO::class, withBodies: true), - Nette\InvalidStateException::class, - 'Source code of PDO not found.', + Nette\NotSupportedException::class, + 'The $withBodies parameter cannot be used for anonymous or internal classes.', ); @@ -23,7 +23,7 @@ Assert::exception( } }, withBodies: true), Nette\NotSupportedException::class, - 'The $withBodies parameter cannot be used for anonymous functions.', + 'The $withBodies parameter cannot be used for anonymous or internal classes.', ); From 47c9380290a0f5727934cb856b647860f04087f6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 15 Oct 2023 01:36:52 +0200 Subject: [PATCH 188/266] Extractor::extractAll() fixed file comment parsing --- src/PhpGenerator/Extractor.php | 6 ++- .../Extractor.extractAll.file.comments.phpt | 43 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/PhpGenerator/Extractor.extractAll.file.comments.phpt diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 51b3419d..c1a71ffd 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -207,7 +207,11 @@ public function enterNode(Node $node) } }; - if ($this->statements) { + if ( + $this->statements + && !$this->statements[0] instanceof Node\Stmt\ClassLike + && !$this->statements[0] instanceof Node\Stmt\Function_ + ) { $this->addCommentAndAttributes($phpFile, $this->statements[0]); } diff --git a/tests/PhpGenerator/Extractor.extractAll.file.comments.phpt b/tests/PhpGenerator/Extractor.extractAll.file.comments.phpt new file mode 100644 index 00000000..03eafa44 --- /dev/null +++ b/tests/PhpGenerator/Extractor.extractAll.file.comments.phpt @@ -0,0 +1,43 @@ +extractAll(); + +Assert::null($file->getComment()); +Assert::same('doc comment', $file->getClasses()['Class1']->getComment()); + + +$file = (new Extractor(<<<'XX' + extractAll(); + +Assert::same('doc comment', $file->getComment()); + + +$file = (new Extractor(<<<'XX' + extractAll(); + +Assert::null($file->getComment()); From 389aed1abe4565d5bdb7c762fd71c2e9f970eef1 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 14 Oct 2023 15:05:27 +0200 Subject: [PATCH 189/266] Extractor: refactoring --- src/PhpGenerator/Extractor.php | 109 +++++++++++++++------------------ src/PhpGenerator/Factory.php | 16 ++--- 2 files changed, 55 insertions(+), 70 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index c1a71ffd..8976dbc6 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -180,7 +180,7 @@ public function enterNode(Node $node) } }; - $visitor->callback = function (Node $node) use (&$class, &$namespace, $phpFile) { + $visitor->callback = function (Node $node) use (&$namespace, $phpFile) { if ($node instanceof Node\Stmt\Class_ && !$node->name) { return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; } @@ -189,17 +189,9 @@ public function enterNode(Node $node) && $node->key->name === 'strict_types' && $node->value instanceof Node\Scalar\LNumber => $phpFile->setStrictTypes((bool) $node->value->value), $node instanceof Node\Stmt\Namespace_ => $namespace = $node->name?->toString(), - $node instanceof Node\Stmt\Use_ => $this->addUseToNamespace($node, $phpFile->addNamespace($namespace)), - $node instanceof Node\Stmt\Class_ => $class = $this->addClassToFile($phpFile, $node), - $node instanceof Node\Stmt\Interface_ => $class = $this->addInterfaceToFile($phpFile, $node), - $node instanceof Node\Stmt\Trait_ => $class = $this->addTraitToFile($phpFile, $node), - $node instanceof Node\Stmt\Enum_ => $class = $this->addEnumToFile($phpFile, $node), + $node instanceof Node\Stmt\Use_ => $this->addUseToNamespace($phpFile->addNamespace($namespace), $node), + $node instanceof Node\Stmt\ClassLike => $this->addClassLikeToFile($phpFile, $node), $node instanceof Node\Stmt\Function_ => $this->addFunctionToFile($phpFile, $node), - $node instanceof Node\Stmt\TraitUse => $this->addTraitToClass($class, $node), - $node instanceof Node\Stmt\Property => $this->addPropertyToClass($class, $node), - $node instanceof Node\Stmt\ClassMethod => $this->addMethodToClass($class, $node), - $node instanceof Node\Stmt\ClassConst => $this->addConstantToClass($class, $node), - $node instanceof Node\Stmt\EnumCase => $this->addEnumCaseToClass($class, $node), default => null, }; if ($node instanceof Node\FunctionLike) { @@ -222,7 +214,7 @@ public function enterNode(Node $node) } - private function addUseToNamespace(Node\Stmt\Use_ $node, PhpNamespace $namespace): void + private function addUseToNamespace(PhpNamespace $namespace, Node\Stmt\Use_ $node): void { $of = [ $node::TYPE_NORMAL => PhpNamespace::NameNormal, @@ -235,62 +227,53 @@ private function addUseToNamespace(Node\Stmt\Use_ $node, PhpNamespace $namespace } - private function addClassToFile(PhpFile $phpFile, Node\Stmt\Class_ $node): ClassType + private function addClassLikeToFile(PhpFile $phpFile, Node\Stmt\ClassLike $node): ClassLike { - $class = $phpFile->addClass($node->namespacedName->toString()); - if ($node->extends) { - $class->setExtends($node->extends->toString()); - } - - foreach ($node->implements as $item) { - $class->addImplement($item->toString()); - } - - $class->setFinal($node->isFinal()); - $class->setAbstract($node->isAbstract()); - $class->setReadOnly(method_exists($node, 'isReadonly') && $node->isReadonly()); - $this->addCommentAndAttributes($class, $node); - return $class; - } - - - private function addInterfaceToFile(PhpFile $phpFile, Node\Stmt\Interface_ $node): InterfaceType - { - $class = $phpFile->addInterface($node->namespacedName->toString()); - foreach ($node->extends as $item) { - $class->addExtend($item->toString()); + if ($node instanceof Node\Stmt\Class_) { + $class = $phpFile->addClass($node->namespacedName->toString()); + $class->setFinal($node->isFinal()); + $class->setAbstract($node->isAbstract()); + $class->setReadOnly(method_exists($node, 'isReadonly') && $node->isReadonly()); + if ($node->extends) { + $class->setExtends($node->extends->toString()); + } + foreach ($node->implements as $item) { + $class->addImplement($item->toString()); + } + } elseif ($node instanceof Node\Stmt\Interface_) { + $class = $phpFile->addInterface($node->namespacedName->toString()); + foreach ($node->extends as $item) { + $class->addExtend($item->toString()); + } + } elseif ($node instanceof Node\Stmt\Trait_) { + $class = $phpFile->addTrait($node->namespacedName->toString()); + + } elseif ($node instanceof Node\Stmt\Enum_) { + $class = $phpFile->addEnum($node->namespacedName->toString()); + $class->setType($node->scalarType?->toString()); + foreach ($node->implements as $item) { + $class->addImplement($item->toString()); + } } $this->addCommentAndAttributes($class, $node); + $this->addClassMembers($class, $node); return $class; } - private function addTraitToFile(PhpFile $phpFile, Node\Stmt\Trait_ $node): TraitType - { - $class = $phpFile->addTrait($node->namespacedName->toString()); - $this->addCommentAndAttributes($class, $node); - return $class; - } - - - private function addEnumToFile(PhpFile $phpFile, Node\Stmt\Enum_ $node): EnumType + private function addClassMembers(ClassLike $class, Node\Stmt\ClassLike $node): void { - $enum = $phpFile->addEnum($node->namespacedName->toString()); - $enum->setType($node->scalarType?->toString()); - foreach ($node->implements as $item) { - $enum->addImplement($item->toString()); + foreach ($node->stmts as $stmt) { + match (true) { + $stmt instanceof Node\Stmt\TraitUse => $this->addTraitToClass($class, $stmt), + $stmt instanceof Node\Stmt\Property => $this->addPropertyToClass($class, $stmt), + $stmt instanceof Node\Stmt\ClassMethod => $this->addMethodToClass($class, $stmt), + $stmt instanceof Node\Stmt\ClassConst => $this->addConstantToClass($class, $stmt), + $stmt instanceof Node\Stmt\EnumCase => $this->addEnumCaseToClass($class, $stmt), + default => null, + }; } - - $this->addCommentAndAttributes($enum, $node); - return $enum; - } - - - private function addFunctionToFile(PhpFile $phpFile, Node\Stmt\Function_ $node): void - { - $function = $phpFile->addFunction($node->namespacedName->toString()); - $this->setupFunction($function, $node); } @@ -359,6 +342,13 @@ private function addEnumCaseToClass(EnumType $class, Node\Stmt\EnumCase $node): } + private function addFunctionToFile(PhpFile $phpFile, Node\Stmt\Function_ $node): void + { + $function = $phpFile->addFunction($node->namespacedName->toString()); + $this->setupFunction($function, $node); + } + + private function addCommentAndAttributes( PhpFile|ClassLike|Constant|Property|GlobalFunction|Method|Parameter|EnumCase|TraitUse $element, Node $node, @@ -411,7 +401,8 @@ private function setupFunction(GlobalFunction|Method $function, Node\FunctionLik $this->addCommentAndAttributes($function, $node); if ($node->getStmts()) { - $function->setBody($this->getReformattedContents($node->getStmts(), 2)); + $indent = $function instanceof GlobalFunction ? 1 : 2; + $function->setBody($this->getReformattedContents($node->getStmts(), $indent)); } } diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 1e34ad8c..9ab18d92 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -101,7 +101,7 @@ public function fromClassReflection( $methods[] = $m = $this->fromMethodReflection($method); if ($withBodies) { $bodies = &$this->bodyCache[$declaringClass->name]; - $bodies ??= $this->getExtractor($declaringClass)->extractMethodBodies($declaringClass->name); + $bodies ??= $this->getExtractor($declaringClass->getFileName())->extractMethodBodies($declaringClass->name); if (isset($bodies[$declaringMethod->name])) { $m->setBody($bodies[$declaringMethod->name]); } @@ -181,7 +181,7 @@ public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody throw new Nette\NotSupportedException('The $withBody parameter cannot be used for closures or internal functions.'); } - $function->setBody($this->getExtractor($from)->extractFunctionBody($from->name)); + $function->setBody($this->getExtractor($from->getFileName())->extractFunctionBody($from->name)); } return $function; @@ -309,16 +309,10 @@ private function getVisibility($from): string } - private function getExtractor($from): Extractor + private function getExtractor(string $file): Extractor { - $file = $from->getFileName(); $cache = &$this->extractorCache[$file]; - if ($cache !== null) { - return $cache; - } elseif (!$file) { - throw new Nette\InvalidStateException("Source code of $from->name not found."); - } - - return new Extractor(file_get_contents($file)); + $cache ??= new Extractor(file_get_contents($file)); + return $cache; } } From a0f1aefb11af11d12e08ddd60f00e4dc85aa4fac Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 15 Oct 2023 00:54:09 +0200 Subject: [PATCH 190/266] Extractor::extractAll() extracts only top-level classes and functions --- src/PhpGenerator/Extractor.php | 57 ++++++++++------------- tests/PhpGenerator/fixtures/extractor.php | 6 +++ 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 8976dbc6..a2cf82f3 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -169,35 +169,6 @@ private function performReplacements(string $s, array $replacements): string public function extractAll(): PhpFile { $phpFile = new PhpFile; - $namespace = ''; - $visitor = new class extends PhpParser\NodeVisitorAbstract { - public $callback; - - - public function enterNode(Node $node) - { - return ($this->callback)($node); - } - }; - - $visitor->callback = function (Node $node) use (&$namespace, $phpFile) { - if ($node instanceof Node\Stmt\Class_ && !$node->name) { - return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; - } - match (true) { - $node instanceof Node\Stmt\DeclareDeclare - && $node->key->name === 'strict_types' - && $node->value instanceof Node\Scalar\LNumber => $phpFile->setStrictTypes((bool) $node->value->value), - $node instanceof Node\Stmt\Namespace_ => $namespace = $node->name?->toString(), - $node instanceof Node\Stmt\Use_ => $this->addUseToNamespace($phpFile->addNamespace($namespace), $node), - $node instanceof Node\Stmt\ClassLike => $this->addClassLikeToFile($phpFile, $node), - $node instanceof Node\Stmt\Function_ => $this->addFunctionToFile($phpFile, $node), - default => null, - }; - if ($node instanceof Node\FunctionLike) { - return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; - } - }; if ( $this->statements @@ -207,9 +178,31 @@ public function enterNode(Node $node) $this->addCommentAndAttributes($phpFile, $this->statements[0]); } - $traverser = new PhpParser\NodeTraverser; - $traverser->addVisitor($visitor); - $traverser->traverse($this->statements); + $namespaces = ['' => $this->statements]; + foreach ($this->statements as $node) { + if ($node instanceof Node\Stmt\Declare_ + && $node->declares[0] instanceof Node\Stmt\DeclareDeclare + && $node->declares[0]->key->name === 'strict_types' + && $node->declares[0]->value instanceof Node\Scalar\LNumber + ) { + $phpFile->setStrictTypes((bool) $node->declares[0]->value->value); + + } elseif ($node instanceof Node\Stmt\Namespace_) { + $namespaces[$node->name->toString()] = $node->stmts; + } + } + + foreach ($namespaces as $name => $nodes) { + foreach ($nodes as $node) { + match (true) { + $node instanceof Node\Stmt\Use_ => $this->addUseToNamespace($phpFile->addNamespace($name), $node), + $node instanceof Node\Stmt\ClassLike => $this->addClassLikeToFile($phpFile, $node), + $node instanceof Node\Stmt\Function_ => $this->addFunctionToFile($phpFile, $node), + default => null, + }; + } + } + return $phpFile; } diff --git a/tests/PhpGenerator/fixtures/extractor.php b/tests/PhpGenerator/fixtures/extractor.php index fafe8004..936b5c0a 100644 --- a/tests/PhpGenerator/fixtures/extractor.php +++ b/tests/PhpGenerator/fixtures/extractor.php @@ -37,3 +37,9 @@ function bar() { } } + +if (false) { + class Class2 + { + } +} From d915b7fa643cd0cf6ffe306c25ac5c3db24914a4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 15 Oct 2023 02:03:34 +0200 Subject: [PATCH 191/266] Extractor: extracts native PHP values --- src/PhpGenerator/Extractor.php | 51 ++++++++++--- .../Extractor.extractAll.vars.phpt | 72 +++++++++++++++++++ 2 files changed, 113 insertions(+), 10 deletions(-) create mode 100644 tests/PhpGenerator/Extractor.extractAll.vars.phpt diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index a2cf82f3..fd25c1d1 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -292,7 +292,7 @@ private function addPropertyToClass(ClassLike $class, Node\Stmt\Property $node): $prop->setVisibility($this->toVisibility($node->flags)); $prop->setType($node->type ? $this->toPhp($node->type) : null); if ($item->default) { - $prop->setValue($this->formatValue($item->default, 1)); + $prop->setValue($this->toValue($item->default)); } $prop->setReadOnly(method_exists($node, 'isReadonly') && $node->isReadonly()); @@ -315,7 +315,7 @@ private function addMethodToClass(ClassLike $class, Node\Stmt\ClassMethod $node) private function addConstantToClass(ClassLike $class, Node\Stmt\ClassConst $node): void { foreach ($node->consts as $item) { - $const = $class->addConstant($item->name->toString(), $this->formatValue($item->value, 1)); + $const = $class->addConstant($item->name->toString(), $this->toValue($item->value)); $const->setVisibility($this->toVisibility($node->flags)); $const->setFinal(method_exists($node, 'isFinal') && $node->isFinal()); $this->addCommentAndAttributes($const, $node); @@ -328,7 +328,7 @@ private function addEnumCaseToClass(EnumType $class, Node\Stmt\EnumCase $node): $value = match (true) { $node->expr === null => null, $node->expr instanceof Node\Scalar\LNumber, $node->expr instanceof Node\Scalar\String_ => $node->expr->value, - default => $this->formatValue($node->expr, 1), + default => $this->toValue($node->expr), }; $case = $class->addCase($node->name->toString(), $value); $this->addCommentAndAttributes($case, $node); @@ -358,11 +358,10 @@ private function addCommentAndAttributes( foreach ($group->attrs as $attribute) { $args = []; foreach ($attribute->args as $arg) { - $value = $this->formatValue($arg->value, 0); if ($arg->name) { - $args[$arg->name->toString()] = $value; + $args[$arg->name->toString()] = $this->toValue($arg->value); } else { - $args[] = $value; + $args[] = $this->toValue($arg->value); } } @@ -386,7 +385,7 @@ private function setupFunction(GlobalFunction|Method $function, Node\FunctionLik $param->setReference($item->byRef); $function->setVariadic($item->variadic); if ($item->default) { - $param->setDefaultValue($this->formatValue($item->default, 2)); + $param->setDefaultValue($this->toValue($item->default)); } $this->addCommentAndAttributes($param, $item); @@ -400,10 +399,42 @@ private function setupFunction(GlobalFunction|Method $function, Node\FunctionLik } - private function formatValue(Node\Expr $value, int $level): Literal + private function toValue(Node\Expr $node): mixed { - $value = $this->getReformattedContents([$value], $level); - return new Literal($value); + if ($node instanceof Node\Expr\ConstFetch) { + return match ($node->name->toLowerString()) { + 'null' => null, + 'true' => true, + 'false' => false, + default => new Literal($this->getReformattedContents([$node], 0)), + }; + } elseif ($node instanceof Node\Scalar\LNumber + || $node instanceof Node\Scalar\DNumber + || $node instanceof Node\Scalar\String_ + ) { + return $node->value; + + } elseif ($node instanceof Node\Expr\Array_) { + $res = []; + foreach ($node->items as $item) { + if ($item->unpack) { + $res[] = new Literal($this->getReformattedContents([$item], 0)); + + } elseif ($item->key) { + $key = $item->key instanceof Node\Identifier + ? $item->key->name + : $this->toValue($item->key); + $res[$key] = $this->toValue($item->value); + + } else { + $res[] = $this->toValue($item->value); + } + } + return $res; + + } else { + return new Literal($this->getReformattedContents([$node], 0)); + } } diff --git a/tests/PhpGenerator/Extractor.extractAll.vars.phpt b/tests/PhpGenerator/Extractor.extractAll.vars.phpt new file mode 100644 index 00000000..464c4a38 --- /dev/null +++ b/tests/PhpGenerator/Extractor.extractAll.vars.phpt @@ -0,0 +1,72 @@ + [3]], ...self::Foo]; + public $concat = 'x' . 'y'; + public $math = 10 * 3; + + public function foo($a = [1, 2, 3], $b = new stdClass(1, 2)) + { + } + } + XX))->extractAll(); + + +$class = $file->getClasses()['Class1']; +Assert::equal( + new Attribute('Attr', [1, 'foo' => 2, 'bar' => new Literal('new /*(n*/\Attr(3)')]), + $class->getAttributes()[0], +); + +Assert::same([1], $class->getConstant('Foo')->getValue()); + +Assert::same(null, $class->getProperty('null')->getValue()); +Assert::same( + [true, false, 1, 1.0, 'hello'], + $class->getProperty('scalar')->getValue(), +); +Assert::equal( + [new Literal('/*(c*/\PHP_VERSION'), new Literal('self::Foo')], + $class->getProperty('const')->getValue(), +); +Assert::equal( + [1, 2, ['x' => [3]], new Literal('...self::Foo')], + $class->getProperty('array')->getValue(), +); +Assert::equal( + new Literal("'x' . 'y'"), + $class->getProperty('concat')->getValue(), +); +Assert::equal( + new Literal('10 * 3'), + $class->getProperty('math')->getValue(), +); + +$method = $class->getMethod('foo'); +Assert::same( + [1, 2, 3], + $method->getParameter('a')->getDefaultValue(), +); +Assert::equal( + new Literal('new /*(n*/\stdClass(1, 2)'), + $method->getParameter('b')->getDefaultValue(), +); From 0106e52d0d69f5f9eec55809623a065fba7d42a1 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 29 Oct 2023 23:27:06 +0100 Subject: [PATCH 192/266] Extractor: fixed extracting of special arrays [Closes #143] --- src/PhpGenerator/Extractor.php | 7 ++++++- tests/PhpGenerator/Extractor.extractAll.vars.phpt | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index fd25c1d1..a6259dab 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -418,12 +418,17 @@ private function toValue(Node\Expr $node): mixed $res = []; foreach ($node->items as $item) { if ($item->unpack) { - $res[] = new Literal($this->getReformattedContents([$item], 0)); + return new Literal($this->getReformattedContents([$node], 0)); } elseif ($item->key) { $key = $item->key instanceof Node\Identifier ? $item->key->name : $this->toValue($item->key); + + if ($key instanceof Literal) { + return new Literal($this->getReformattedContents([$node], 0)); + } + $res[$key] = $this->toValue($item->value); } else { diff --git a/tests/PhpGenerator/Extractor.extractAll.vars.phpt b/tests/PhpGenerator/Extractor.extractAll.vars.phpt index 464c4a38..b5c183b0 100644 --- a/tests/PhpGenerator/Extractor.extractAll.vars.phpt +++ b/tests/PhpGenerator/Extractor.extractAll.vars.phpt @@ -20,7 +20,9 @@ $file = (new Extractor(<<<'XX' public $null = null; public $scalar = [true, false, 1, 1.0, 'hello']; public $const = [PHP_VERSION, self::Foo]; - public $array = [1, 2, ['x' => [3]], ...self::Foo]; + public $array = [1, 2, ['x' => [3]]]; + public $arraySpec1 = [...self::Foo]; + public $arraySpec2 = [self::class => 1]; public $concat = 'x' . 'y'; public $math = 10 * 3; @@ -49,9 +51,17 @@ Assert::equal( $class->getProperty('const')->getValue(), ); Assert::equal( - [1, 2, ['x' => [3]], new Literal('...self::Foo')], + [1, 2, ['x' => [3]]], $class->getProperty('array')->getValue(), ); +Assert::equal( + new Literal('[...self::Foo]'), + $class->getProperty('arraySpec1')->getValue(), +); +Assert::equal( + new Literal('[self::class => 1]'), + $class->getProperty('arraySpec2')->getValue(), +); Assert::equal( new Literal("'x' . 'y'"), $class->getProperty('concat')->getValue(), From abc0e79b2d02d4b8aba5933765b90df3f610c143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 29 Sep 2023 12:28:32 +0200 Subject: [PATCH 193/266] Support union type in Type::nullable (#141) --- readme.md | 3 ++- src/PhpGenerator/Type.php | 22 +++++++++++++++- tests/PhpGenerator/Type.phpt | 50 +++++++++++++++++++++++++++++++----- 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/readme.md b/readme.md index 33ba4332..aa96e1d5 100644 --- a/readme.md +++ b/readme.md @@ -538,7 +538,8 @@ Each type or union/intersection type can be passed as a string, you can also use use Nette\PhpGenerator\Type; $member->setType('array'); // or Type::Array; -$member->setType('array|string'); // or Type::union('array', 'string') +$member->setType('?array'); // or Type::nullable(Type::Array); +$member->setType('array|string'); // or Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // or Type::intersection(Foo::class, Bar::class) $member->setType(null); // removes type ``` diff --git a/src/PhpGenerator/Type.php b/src/PhpGenerator/Type.php index d122ab71..0b519e57 100644 --- a/src/PhpGenerator/Type.php +++ b/src/PhpGenerator/Type.php @@ -9,6 +9,8 @@ namespace Nette\PhpGenerator; +use Nette; + /** * PHP return, property and parameter types. @@ -85,7 +87,25 @@ class Type public static function nullable(string $type, bool $nullable = true): string { - return ($nullable ? '?' : '') . ltrim($type, '?'); + if (str_contains($type, '&')) { + return $nullable + ? throw new Nette\InvalidArgumentException('Intersection types cannot be nullable.') + : $type; + } + + $nnType = preg_replace('#^\?|^null\||\|null(?=\||$)#i', '', $type); + $always = (bool) preg_match('#^(null|mixed)$#i', $nnType); + if ($nullable) { + return match (true) { + $always, $type !== $nnType => $type, + str_contains($type, '|') => $type . '|null', + default => '?' . $type, + }; + } else { + return $always + ? throw new Nette\InvalidArgumentException("Type $type cannot be not nullable.") + : $nnType; + } } diff --git a/tests/PhpGenerator/Type.phpt b/tests/PhpGenerator/Type.phpt index aa737db6..a2f54cee 100644 --- a/tests/PhpGenerator/Type.phpt +++ b/tests/PhpGenerator/Type.phpt @@ -6,12 +6,50 @@ use Nette\PhpGenerator\Type; use Tester\Assert; require __DIR__ . '/../bootstrap.php'; +// Nullable +Assert::same('?int', Type::nullable(Type::Int)); +Assert::same('int', Type::nullable(Type::Int, nullable: false)); -Assert::same('A|string', Type::union(A::class, Type::String)); +Assert::same('?int', Type::nullable('?int')); +Assert::same('int', Type::nullable('?int', nullable: false)); + +Assert::same('null', Type::nullable('null')); +Assert::same('NULL', Type::nullable('NULL')); +Assert::exception( + fn() => Type::nullable('null', nullable: false), + Nette\InvalidArgumentException::class, + 'Type null cannot be not nullable.', +); + +Assert::same('mixed', Type::nullable('mixed')); +Assert::exception( + fn() => Type::nullable('mixed', nullable: false), + Nette\InvalidArgumentException::class, + 'Type mixed cannot be not nullable.', +); + +Assert::same('int|float|string|null', Type::nullable('int|float|string')); +Assert::same('int|float|string', Type::nullable('int|float|string', nullable: false)); + +Assert::same('NULL|int|float|string', Type::nullable('NULL|int|float|string')); +Assert::same('int|float|string', Type::nullable('NULL|int|float|string', nullable: false)); -Assert::same('?A', Type::nullable(A::class)); -Assert::same('?A', Type::nullable(A::class)); -Assert::same('A', Type::nullable(A::class, nullable: false)); +Assert::same('int|float|string|null', Type::nullable('int|float|string|null')); +Assert::same('int|float|string', Type::nullable('int|float|string|null', nullable: false)); + +Assert::same('int|float|null|string', Type::nullable('int|float|null|string')); +Assert::same('int|float|string', Type::nullable('int|float|null|string', nullable: false)); + +Assert::exception( + fn() => Type::nullable('Foo&Bar'), + Nette\InvalidArgumentException::class, + 'Intersection types cannot be nullable.', +); +Assert::same('Foo&Bar', Type::nullable('Foo&Bar', nullable: false)); + + +// Union +Assert::same('A|string', Type::union(A::class, Type::String)); -Assert::same('?A', Type::nullable('?A')); -Assert::same('A', Type::nullable('?A', nullable: false)); +// Intersection +Assert::same('A&string', Type::intersection(A::class, Type::String)); From 561dc1a34935e96b28abd62de9d01e100e57235f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 1 Nov 2023 13:58:32 +0100 Subject: [PATCH 194/266] readme: better wording --- readme.md | 250 +++++++++++++++++++++++++++--------------------------- 1 file changed, 127 insertions(+), 123 deletions(-) diff --git a/readme.md b/readme.md index aa96e1d5..2f7da038 100644 --- a/readme.md +++ b/readme.md @@ -1,16 +1,19 @@ Nette PHP Generator [![Latest Stable Version](https://poser.pugx.org/nette/php-generator/v/stable)](https://github.com/nette/php-generator/releases) [![Downloads this Month](https://img.shields.io/packagist/dm/nette/php-generator.svg)](https://packagist.org/packages/nette/php-generator) =================== -✅ Need to generate PHP code for [classes](#classes), [functions](#global-function), [PHP files](#php-files), etc.?
+Are you looking for a tool to generate PHP code for [classes](#classes), [functions](#global-functions), or complete [PHP files](#php-files)? + ✅ Supports all the latest PHP features like [enums](#enums), [attributes](#attributes), etc.
-✅ Allows you to easily modify [existing classes](#generating-according-to-existing-ones)
-✅ [PSR-12 compliant](#printers-and-psr-compliance) output
+✅ Allows you to easily modify [existing classes](#generating-from-existing-ones)
+✅ Output compliant with [PSR-12 / PER coding style](#printer-and-psr-compliance)
✅ Highly mature, stable, and widely used library Installation ------------ +Download and install the library using the [Composer](https://doc.nette.org/en/best-practices/composer) tool: + ```shell composer require nette/php-generator ``` @@ -31,7 +34,7 @@ Thank you! Classes ------- -Let's start with a straightforward example of generating class using [ClassType](https://api.nette.org/php-generator/master/Nette/PhpGenerator/ClassType.html): +Let's start with an example of creating a class using [ClassType](https://api.nette.org/php-generator/master/Nette/PhpGenerator/ClassType.html): ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -40,18 +43,18 @@ $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) - ->addComment("Description of class.\nSecond line\n") + ->addComment("Class description.\nSecond line\n") ->addComment('@property-read Nette\Forms\Form $form'); -// to generate PHP code simply cast to string or use echo: +// generate code simply by typecasting to string or using echo: echo $class; ``` -It will render this result: +This will return: ```php /** - * Description of class. + * Class description * Second line * * @property-read Nette\Forms\Form $form @@ -61,18 +64,18 @@ final class Demo extends ParentClass implements Countable } ``` -We can also use a printer to generate the code, which, unlike `echo $class`, we will be able to [further configure](#printers-and-psr-compliance): +To generate the code, you can also use a so-called printer, which, unlike `echo $class`, can be [further configured](#printer-and-psr-compliance): ```php $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); ``` -We can add constants (class [Constant](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Constant.html)) and properties (class [Property](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Property.html)): +You can add constants (class [Constant](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Constant.html)) and properties (class [Property](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Property.html)): ```php $class->addConstant('ID', 123) - ->setProtected() // constant visiblity + ->setProtected() // constant visibility ->setType('int') ->setFinal(); @@ -83,10 +86,10 @@ $class->addProperty('items', [1, 2, 3]) $class->addProperty('list') ->setType('?array') - ->setInitialized(); // prints '= null' + ->setInitialized(); // outputs '= null' ``` -It generates: +This will generate: ```php final protected const int ID = 123; @@ -97,14 +100,14 @@ private static $items = [1, 2, 3]; public ?array $list = null; ``` -And we can add [methods](#Method-and-Function-Signature): +And you can add [methods](#method-and-function-signatures): ```php $method = $class->addMethod('count') ->addComment('Count it.') ->setFinal() ->setProtected() - ->setReturnType('?int') // method return type + ->setReturnType('?int') // return types for methods ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] @@ -112,7 +115,7 @@ $method->addParameter('items', []) // $items = [] ->setType('array'); // array &$items = [] ``` -It results in: +The result is: ```php /** @@ -124,7 +127,7 @@ final protected function count(array &$items = []): ?int } ``` -Promoted parameters introduced by PHP 8.0 can be passed to the constructor: +Promoted parameters introduced in PHP 8.0 can be passed to the constructor: ```php $method = $class->addMethod('__construct'); @@ -133,7 +136,7 @@ $method->addPromotedParameter('args', []) ->setPrivate(); ``` -It results in: +The result is: ```php public function __construct( @@ -143,15 +146,15 @@ public function __construct( } ``` -Readonly properties and classes can be marked via `setReadOnly()`. +Readonly properties and classes be marked using the `setReadOnly()` function. ------ -If the added property, constant, method or parameter already exist, it throws exception. +If an added property, constant, method, or parameter already exists, an exception is thrown. -Members can be removed using `removeProperty()`, `removeConstant()`, `removeMethod()` or `removeParameter()`. +Class members can be removed using `removeProperty()`, `removeConstant()`, `removeMethod()`, or `removeParameter()`. -You can also add existing `Method`, `Property` or `Constant` objects to the class: +You can also add existing `Method`, `Property`, or `Constant` objects to the class: ```php $method = new Nette\PhpGenerator\Method('getHandle'); @@ -164,7 +167,7 @@ $class = (new Nette\PhpGenerator\ClassType('Demo')) ->addMember($const); ``` -You can clone existing methods, properties and constants with a different name using `cloneWithName()`: +You can also clone existing methods, properties, and constants under a different name using `cloneWithName()`: ```php $methodCount = $class->getMethod('count'); @@ -173,8 +176,8 @@ $class->addMember($methodRecount); ``` -Interface or Trait ------------------- +Interfaces or Traits +-------------------- You can create interfaces and traits (classes [InterfaceType](https://api.nette.org/php-generator/master/Nette/PhpGenerator/InterfaceType.html) and [TraitType](https://api.nette.org/php-generator/master/Nette/PhpGenerator/TraitType.html)): @@ -183,7 +186,7 @@ $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Using traits: +Using a trait: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -194,7 +197,7 @@ $class->addTrait('MyTrait') echo $class; ``` -Result: +The result is: ```php class Demo @@ -211,7 +214,7 @@ class Demo Enums ----- -You can easily create the enums that PHP 8.1 brings (class [EnumType](https://api.nette.org/php-generator/master/Nette/PhpGenerator/EnumType.html)): +You can easily create enums introduced in PHP 8.1 like this (class [EnumType](https://api.nette.org/php-generator/master/Nette/PhpGenerator/EnumType.html)): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -223,7 +226,7 @@ $enum->addCase('Spades'); echo $enum; ``` -Result: +The result is: ```php enum Suit @@ -235,20 +238,20 @@ enum Suit } ``` -You can also define scalar equivalents for cases to create a backed enum: +You can also define scalar equivalents and create a "backed" enum: ```php $enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦'); ``` -It is possible to add a comment or [attributes](#attributes) to each case using `addComment()` or `addAttribute()`. +For each *case*, you can add a comment or [attributes](#attributes) using `addComment()` or `addAttribute()`. -Anonymous Class ---------------- +Anonymous Classes +----------------- -Give `null` as the name and you have an anonymous class: +Pass `null` as the name, and you have an anonymous class: ```php $class = new Nette\PhpGenerator\ClassType(null); @@ -258,7 +261,7 @@ $class->addMethod('__construct') echo '$obj = new class ($val) ' . $class . ';'; ``` -Result: +The result is: ```php $obj = new class ($val) { @@ -270,10 +273,10 @@ $obj = new class ($val) { ``` -Global Function ---------------- +Global Functions +---------------- -Code of functions will generate class [GlobalFunction](https://api.nette.org/php-generator/master/Nette/PhpGenerator/GlobalFunction.html): +The code for functions is generated by the class [GlobalFunction](https://api.nette.org/php-generator/master/Nette/PhpGenerator/GlobalFunction.html): ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -282,11 +285,11 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 / PER +// or use the PsrPrinter for output compliant with PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` -Result: +The result is: ```php function foo($a, $b) @@ -296,10 +299,10 @@ function foo($a, $b) ``` -Closure -------- +Anonymous Functions +------------------- -Code of closures will generate class [Closure](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Closure.html): +The code for anonymous functions is generated by the class [Closure](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Closure.html): ```php $closure = new Nette\PhpGenerator\Closure; @@ -310,11 +313,11 @@ $closure->addUse('c') ->setReference(); echo $closure; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 / PER +// or use the PsrPrinter for output compliant with PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` -Result: +The result is: ```php function ($a, $b) use (&$c) { @@ -323,10 +326,10 @@ function ($a, $b) use (&$c) { ``` -Arrow Function --------------- +Short Arrow Functions +--------------------- -You can also print closure as arrow function using printer: +You can also output a short anonymous function using the printer: ```php $closure = new Nette\PhpGenerator\Closure; @@ -337,17 +340,17 @@ $closure->addParameter('b'); echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure); ``` -Result: +The result is: ```php fn($a, $b) => $a + $b ``` -Method and Function Signature ------------------------------ +Method and Function Signatures +------------------------------ -Methods are represented by the class [Method](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Method.html). You can set visibility, return value, add comments, [attributes|#Attributes] etc: +Methods are represented by the class [Method](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Method.html). You can set visibility, return value, add comments, [attributes](#attributes), etc.: ```php $method = $class->addMethod('count') @@ -357,12 +360,12 @@ $method = $class->addMethod('count') ->setReturnType('?int'); ``` -Each parameter is represented by a class [Parameter](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Parameter.html). Again, you can set every conceivable property: +Individual parameters are represented by the class [Parameter](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Parameter.html). Again, you can set all conceivable properties: ```php $method->addParameter('items', []) // $items = [] - ->setReference() // &$items = [] - ->setType('array'); // array &$items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] // function count(&$items = []) ``` @@ -375,7 +378,7 @@ $method->setVariadics(true); $method->addParameter('items'); ``` -Generates: +This generates: ```php function count(...$items) @@ -384,10 +387,10 @@ function count(...$items) ``` -Method and Function Body ------------------------- +Method and Function Bodies +-------------------------- -The body can be passed to the `setBody()` method at once or sequentially (line by line) by repeatedly calling `addBody()`: +The body can be passed all at once to the `setBody()` method or gradually (line by line) by repeatedly calling `addBody()`: ```php $function = new Nette\PhpGenerator\GlobalFunction('foo'); @@ -396,7 +399,7 @@ $function->addBody('return $a;'); echo $function; ``` -Result +The result is: ```php function foo() @@ -406,7 +409,7 @@ function foo() } ``` -You can use special placeholders for handy way to inject variables. +You can use special placeholders for easy variable insertion. Simple placeholders `?` @@ -418,7 +421,7 @@ $function->addBody('return substr(?, ?);', [$str, $num]); echo $function; ``` -Result: +The result is: ```php function foo() @@ -427,7 +430,7 @@ function foo() } ``` -Variadic placeholder `...?` +Placeholder for variadic `...?` ```php $items = [1, 2, 3]; @@ -436,7 +439,7 @@ $function->setBody('myfunc(...?);', [$items]); echo $function; ``` -Result: +The result is: ```php function foo() @@ -445,7 +448,7 @@ function foo() } ``` -You can also use PHP 8 named parameters using placeholder `...?:` +You can also use named parameters for PHP 8 with `...?:` ```php $items = ['foo' => 1, 'bar' => true]; @@ -454,7 +457,7 @@ $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true); ``` -Escape placeholder using slash `\?` +The placeholder is escaped with a backslash `\?` ```php $num = 3; @@ -464,7 +467,7 @@ $function->addBody('return $a \? 10 : ?;', [$num]); echo $function; ``` -Result: +The result is: ```php function foo($a) @@ -474,10 +477,10 @@ function foo($a) ``` -Printers and PSR Compliance ---------------------------- +Printer and PSR Compliance +-------------------------- -The [Printer](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Printer.html) class is used to generate PHP code: +The [Printer](https://api.nette.org/php-generator/master/Nette/PhpGenerator/Printer.html) class is used for generating PHP code: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -487,16 +490,16 @@ $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); // same as: echo $class ``` -It can generate code for all other elements, offering methods such as `printFunction()`, `printNamespace()`, etc. +It can generate code for all other elements, offering methods like `printFunction()`, `printNamespace()`, etc. -Additionally, the `PsrPrinter` class is available, whose output is in compliance with the PSR-2 / PSR-12 / PER coding style: +There's also the `PsrPrinter` class, which outputs in accordance with PSR-2 / PSR-12 / PER coding style: ```php $printer = new Nette\PhpGenerator\PsrPrinter; echo $printer->printClass($class); ``` -Need to fine-tune behavior to your needs? Create your own printer by inheriting from the `Printer` class. You can reconfigure these variables: +Need custom behavior? Create your own version by inheriting the `Printer` class. You can reconfigure these variables: ```php class MyPrinter extends Nette\PhpGenerator\Printer @@ -509,11 +512,11 @@ class MyPrinter extends Nette\PhpGenerator\Printer public int $linesBetweenProperties = 0; // number of blank lines between methods public int $linesBetweenMethods = 2; - // number of blank lines between groups of use statements for classes, functions, and constants + // number of blank lines between 'use statements' groups for classes, functions, and constants public int $linesBetweenUseTypes = 0; - // position of the opening brace for functions and methods + // position of the opening curly brace for functions and methods public bool $bracesOnNextLine = true; - // place one parameter in one line, even if it has an attribute or is promoted + // place one parameter on one line, even if it has an attribute or is supported public bool $singleParameterOnOneLine = false; // omits namespaces that do not contain any class or function public bool $omitEmptyNamespaces = true; @@ -522,17 +525,17 @@ class MyPrinter extends Nette\PhpGenerator\Printer } ``` -How and why exactly does the standard `Printer` and `PsrPrinter` differ? Why isn't there just one printer, the `PsrPrinter`, in the package? +How and why does the standard `Printer` differ from `PsrPrinter`? Why isn't there just one printer, the `PsrPrinter`, in the package? -The standard `Printer` formats the code as we do it in all of Nette. Since Nette was created much earlier than PSR, and also because PSR for many years did not deliver standards in time, but sometimes even with several years of delay from the introduction of a new feature in PHP, this resulted in a few minor differences in the coding standard. -The bigger difference is just the use of tabs instead of spaces. We know that by using tabs in our projects we allow for width adjustment, which is [essential for people with visual impairments](https://doc.nette.org/en/contributing/coding-standard#toc-tabs-instead-of-spaces). -An example of a minor difference is the placement of the curly brace on a separate line for functions and methods and always. We see the PSR recommendation as illogical and [leading to a decrease in code clarity](https://doc.nette.org/en/contributing/coding-standard#toc-wrapping-and-braces). +The standard `Printer` formats the code as we do throughout Nette. Since Nette was established much earlier than PSR, and also because PSR took years to deliver standards on time, sometimes even several years after introducing a new feature in PHP, it resulted in a [coding standard](https://doc.nette.org/en/contributing/coding-standard) that differs in a few minor aspects. +The major difference is the use of tabs instead of spaces. We know that by using tabs in our projects, we allow for width customization, which is [essential for people with visual impairments](https://doc.nette.org/en/contributing/coding-standard#toc-tabs-instead-of-spaces). +An example of a minor difference is placing the curly brace on a separate line for functions and methods, always. The PSR recommendation seems illogical to us and [leads to reduced code clarity](https://doc.nette.org/en/contributing/coding-standard#toc-wrapping-and-braces). Types ----- -Each type or union/intersection type can be passed as a string, you can also use predefined constants for native types: +Every type or union/intersection type can be passed as a string; you can also use predefined constants for native types: ```php use Nette\PhpGenerator\Type; @@ -541,16 +544,16 @@ $member->setType('array'); // or Type::Array; $member->setType('?array'); // or Type::nullable(Type::Array); $member->setType('array|string'); // or Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // or Type::intersection(Foo::class, Bar::class) -$member->setType(null); // removes type +$member->setType(null); // removes the type ``` -The same applies to the method `setReturnType()`. +The same applies to the `setReturnType()` method. Literals -------- -With `Literal` you can pass arbitrary PHP code to, for example, default property or parameter values etc: +Using `Literal`, you can pass any PHP code, for example, for default property values or parameters, etc: ```php use Nette\PhpGenerator\Literal; @@ -578,25 +581,25 @@ class Demo } ``` -You can also pass parameters to `Literal` and have it formatted into valid PHP code using [special placeholders](#method-and-function-body-generator): +You can also pass parameters to `Literal` and have them formatted into valid PHP code using [placeholders](#method-and-function-bodies): ```php new Literal('substr(?, ?)', [$a, $b]); -// generates, for example: substr('hello', 5); +// generates for example: substr('hello', 5); ``` -The literal representing the creation of a new object is easily generated by the `new` method: +A literal representing the creation of a new object can easily be generated using the `new` method: ```php Literal::new(Demo::class, [$a, 'foo' => $b]); -// generates, for example: new Demo(10, foo: 20) +// generates for example: new Demo(10, foo: 20) ``` Attributes ---------- -You can add PHP 8 attributes to all classes, methods, properties, constants, enum cases, functions, closures and parameters. [Literals](#literals) can also be used as parameter values. +With PHP 8, you can add attributes to all classes, methods, properties, constants, enum cases, functions, closures, and parameters. You can also use [literals](#literals) as parameter values. ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -642,7 +645,7 @@ class Demo Namespace --------- -Classes, traits, interfaces and enums (hereinafter classes) can be grouped into namespaces (class [PhpNamespace](https://api.nette.org/php-generator/master/Nette/PhpGenerator/PhpNamespace.html)): +Classes, traits, interfaces, and enums (hereafter referred to as classes) can be grouped into namespaces represented by the [PhpNamespace](https://api.nette.org/php-generator/master/Nette/PhpGenerator/PhpNamespace.html) class: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); @@ -657,9 +660,9 @@ $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class); ``` -If the class already exists, it throws exception. +If the class already exists, an exception is thrown. -You can define use-statements: +You can define use clauses: ```php // use Http\Request; @@ -670,14 +673,14 @@ $namespace->addUse(Http\Request::class, 'HttpReq'); $namespace->addUseFunction('iter\range'); ``` -To simplify a fully qualified class, function or constant name according to the defined aliases, use the `simplifyName` method: +To simplify a fully qualified class, function, or constant name based on defined aliases, use the `simplifyName` method: ```php -echo $namespace->simplifyName('Foo\Bar'); // 'Bar', because 'Foo' is current namespace -echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', because of the defined use-statement +echo $namespace->simplifyName('Foo\Bar'); // 'Bar', because 'Foo' is the current namespace +echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', due to the defined use-statement ``` -Conversely, you can convert a simplified class, function or constant name to a fully qualified one using the `resolveName` method: +Conversely, you can convert a simplified class, function, or constant name back to a fully qualified name using the `resolveName` method: ```php echo $namespace->resolveName('Bar'); // 'Foo\Bar' @@ -688,26 +691,25 @@ echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' Class Names Resolving --------------------- -**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, used traits and attributes) 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: +**When a class is part of a namespace, it's rendered slightly differently:** all types (e.g., type hints, return types, parent class name, implemented interfaces, used traits, and attributes) are automatically *resolved* (unless you turn it off, see below). +This means you must use **fully qualified class names** in definitions, and they will be replaced with aliases (based on use clauses) or fully qualified names in the resulting code: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); -$class->addImplement('Foo\A') // it will simplify to A - ->addTrait('Bar\AliasedClass'); // it will simplify to AliasedClass +$class->addImplement('Foo\A') // will be simplified to A + ->addTrait('Bar\AliasedClass'); // will be simplified to AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // in comments simplify manually +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // we manually simplify in comments $method->addParameter('arg') - ->setType('Bar\OtherClass'); // it will resolve to \Bar\OtherClass + ->setType('Bar\OtherClass'); // will be translated to \Bar\OtherClass echo $namespace; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 / PER +// or use the PsrPrinter for output in accordance with PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -743,7 +745,7 @@ echo $printer->printNamespace($namespace); PHP Files --------- -Classes, functions and namespaces can be grouped into PHP files represented by the class [PhpFile](https://api.nette.org/php-generator/master/Nette/PhpGenerator/PhpFile.html): +Classes, functions, and namespaces can be grouped into PHP files represented by the [PhpFile](https://api.nette.org/php-generator/master/Nette/PhpGenerator/PhpFile.html) class: ```php $file = new Nette\PhpGenerator\PhpFile; @@ -760,7 +762,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 / PER +// or use the PsrPrinter for output in accordance with PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -786,9 +788,11 @@ function foo() } ``` +**Please note:** No additional code can be added to the files outside of functions and classes. -Generating According to Existing Ones -------------------------------------- + +Generating from Existing Ones +----------------------------- In addition to being able to model classes and functions using the API described above, you can also have them automatically generated using existing ones: @@ -796,17 +800,17 @@ In addition to being able to model classes and functions using the API described // creates a class identical to the PDO class $class = Nette\PhpGenerator\ClassType::from(PDO::class); -// creates a function identical to trim() +// creates a function identical to the trim() function $function = Nette\PhpGenerator\GlobalFunction::from('trim'); -// creates a closure as specified +// creates a closure based on the provided one $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, ); ``` -Function and method bodies are empty by default. If you want to load them as well, use this way -(it requires `nikic/php-parser` to be installed): +By default, function and method bodies are empty. If you also want to load them, use this method +(requires the `nikic/php-parser` package to be installed): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -815,10 +819,10 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` -Loading from PHP File ---------------------- +Loading from PHP Files +---------------------- -You can also load functions, classes, interfaces and enums directly from a string of PHP code. For example, we create `ClassType` object this way: +You can also load functions, classes, interfaces, and enums directly from a string containing PHP code. For example, to create a `ClassType` object: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<dump($var); // prints ['a', 'b', 123] +echo $dumper->dump($var); // outputs ['a', 'b', 123] ``` From 3162d8dc4b4d31a7bb826e7cf93aa48f60c414b4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 6 Dec 2023 01:13:55 +0100 Subject: [PATCH 195/266] Printer: option $omitEmptyNamespaces is applied by printFile() [Close #147] --- src/PhpGenerator/Printer.php | 8 +++----- tests/PhpGenerator/Printer.use-order.phpt | 1 - 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 32bb8aaf..8a17f049 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -277,10 +277,6 @@ public function printNamespace(PhpNamespace $namespace): string $items[] = $this->printFunction($function, $namespace); } - if (!$items && $this->omitEmptyNamespaces) { - return ''; - } - $body = ($uses ? $uses . "\n" : '') . implode("\n", $items); @@ -300,7 +296,9 @@ public function printFile(PhpFile $file): string { $namespaces = []; foreach ($file->getNamespaces() as $namespace) { - $namespaces[] = $this->printNamespace($namespace); + if (!$this->omitEmptyNamespaces || $namespace->getClasses() || $namespace->getFunctions()) { + $namespaces[] = $this->printNamespace($namespace); + } } return "omitEmptyNamespaces = false; $namespace = new PhpNamespace('Foo'); $namespace->addUse('Example\Foo\EmailAlias\Bar'); $namespace->addUse('Example\Foo\Email\Test'); From 7b931d61774925c0716aca75760234d5bd465509 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 17 Jan 2024 19:44:08 +0100 Subject: [PATCH 196/266] Extractor: NOWDOC/HEREDOC are not converted to single line strings it is necessary to provide an indentation that can be unindented --- src/PhpGenerator/Extractor.php | 87 +++++++--- tests/PhpGenerator/Extractor.strings.phpt | 158 ++++++++++++++++++ .../expected/ClassType.from.bodies.expect | 7 - .../expected/Extractor.bodies.expect | 7 - .../Extractor.bodies.resolving.expect | 7 - .../Extractor.bodies.unresolving.expect | 7 - tests/PhpGenerator/expected/Extractor.expect | 2 +- tests/PhpGenerator/fixtures/bodies.php | 23 --- 8 files changed, 220 insertions(+), 78 deletions(-) create mode 100644 tests/PhpGenerator/Extractor.strings.phpt diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index a6259dab..73208b92 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -95,7 +95,7 @@ public function extractFunctionBody(string $name): ?string private function getReformattedContents(array $nodes, int $level): string { $body = $this->getNodeContents(...$nodes); - $body = $this->performReplacements($body, $this->prepareReplacements($nodes)); + $body = $this->performReplacements($body, $this->prepareReplacements($nodes, $level)); return Helpers::unindent($body, $level); } @@ -104,11 +104,12 @@ private function getReformattedContents(array $nodes, int $level): string * @param Node[] $nodes * @return array */ - private function prepareReplacements(array $nodes): array + private function prepareReplacements(array $nodes, int $level): array { $start = $this->getNodeStartPos($nodes[0]); $replacements = []; - (new NodeFinder)->find($nodes, function (Node $node) use (&$replacements, $start) { + $indent = "\n" . str_repeat("\t", $level); + (new NodeFinder)->find($nodes, function (Node $node) use (&$replacements, $start, $level, $indent) { if ($node instanceof Node\Name\FullyQualified) { if ($node->getAttribute('originalName') instanceof Node\Name) { $of = match (true) { @@ -122,30 +123,64 @@ private function prepareReplacements(array $nodes): array Helpers::tagName($node->toCodeString(), $of), ]; } - } elseif ($node instanceof Node\Scalar\String_ || $node instanceof Node\Scalar\EncapsedStringPart) { - // multi-line strings => singleline - $token = $this->getNodeContents($node); - if (str_contains($token, "\n")) { - $quote = $node instanceof Node\Scalar\String_ ? '"' : ''; - $replacements[] = [ - $node->getStartFilePos() - $start, - $node->getEndFilePos() - $start, - $quote . addcslashes($node->value, "\x00..\x1F") . $quote, - ]; + + } elseif ( + $node instanceof Node\Scalar\String_ + && in_array($node->getAttribute('kind'), [Node\Scalar\String_::KIND_SINGLE_QUOTED, Node\Scalar\String_::KIND_DOUBLE_QUOTED], true) + && str_contains($node->getAttribute('rawValue'), "\n") + ) { // multi-line strings -> single line + $replacements[] = [ + $node->getStartFilePos() - $start, + $node->getEndFilePos() - $start, + '"' . addcslashes($node->value, "\x00..\x1F") . '"', + ]; + + } elseif ( + $node instanceof Node\Scalar\String_ + && in_array($node->getAttribute('kind'), [Node\Scalar\String_::KIND_NOWDOC, Node\Scalar\String_::KIND_HEREDOC], true) + && Helpers::unindent($node->getAttribute('docIndentation'), $level) === $node->getAttribute('docIndentation') + ) { // fix indentation of NOWDOW/HEREDOC + $replacements[] = [ + $node->getStartFilePos() - $start, + $node->getEndFilePos() - $start, + str_replace("\n", $indent, $this->getNodeContents($node)), + ]; + + } elseif ( + $node instanceof Node\Scalar\Encapsed + && $node->getAttribute('kind') === Node\Scalar\String_::KIND_DOUBLE_QUOTED + ) { // multi-line strings -> single line + foreach ($node->parts as $part) { + if ($part instanceof Node\Scalar\EncapsedStringPart) { + $replacements[] = [ + $part->getStartFilePos() - $start, + $part->getEndFilePos() - $start, + addcslashes($part->value, "\x00..\x1F\""), + ]; + } } - } elseif ($node instanceof Node\Scalar\Encapsed) { - // HEREDOC => "string" - if ($node->getAttribute('kind') === Node\Scalar\String_::KIND_HEREDOC) { - $replacements[] = [ - $node->getStartFilePos() - $start, - $node->parts[0]->getStartFilePos() - $start - 1, - '"', - ]; - $replacements[] = [ - end($node->parts)->getEndFilePos() - $start + 1, - $node->getEndFilePos() - $start, - '"', - ]; + } elseif ( + $node instanceof Node\Scalar\Encapsed && $node->getAttribute('kind') === Node\Scalar\String_::KIND_HEREDOC + && Helpers::unindent($node->getAttribute('docIndentation'), $level) === $node->getAttribute('docIndentation') + ) { // fix indentation of HEREDOC + $replacements[] = [ + $tmp = $node->getStartFilePos() - $start + strlen($node->getAttribute('docLabel')) + 3, // <<< + $tmp, + $indent, + ]; + $replacements[] = [ + $tmp = $node->getEndFilePos() - $start - strlen($node->getAttribute('docLabel')), + $tmp, + $indent, + ]; + foreach ($node->parts as $part) { + if ($part instanceof Node\Scalar\EncapsedStringPart) { + $replacements[] = [ + $part->getStartFilePos() - $start, + $part->getEndFilePos() - $start, + str_replace("\n", $indent, $this->getNodeContents($part)), + ]; + } } } }); diff --git a/tests/PhpGenerator/Extractor.strings.phpt b/tests/PhpGenerator/Extractor.strings.phpt new file mode 100644 index 00000000..ed561850 --- /dev/null +++ b/tests/PhpGenerator/Extractor.strings.phpt @@ -0,0 +1,158 @@ +extractFunctionBody('quoted'), +); + + +Assert::match( + <<<'XX' + $s1 = <<extractFunctionBody('heredoc'), +); + + +Assert::match( + <<<'XX' + $s1 = <<<'DOC' + a + b + c 'q1' "q2" + DOC; + + $s2 = <<<'DOC' + a + b + c + DOC; + + $s3 = <<<'DOC' + a + b + c + DOC; + XX, + $extractor->extractFunctionBody('nowdoc'), +); diff --git a/tests/PhpGenerator/expected/ClassType.from.bodies.expect b/tests/PhpGenerator/expected/ClassType.from.bodies.expect index a251549e..1a593c65 100644 --- a/tests/PhpGenerator/expected/ClassType.from.bodies.expect +++ b/tests/PhpGenerator/expected/ClassType.from.bodies.expect @@ -80,13 +80,6 @@ abstract class Class7 comment */ // Alias Method will not be resolved in comment if ($member instanceof Method) { - $s1 = "\na\n\tb\n\t\tc\n"; - $s2 = "\na\n\t{$b}\n\t\t$c\n"; - - $s3 = "a\n\t{$b}\n\t\t$c" - ; - $s3 = "a\n\tb\n\t\tc" - ; // inline HTML is not supported ?> a diff --git a/tests/PhpGenerator/expected/Extractor.bodies.expect b/tests/PhpGenerator/expected/Extractor.bodies.expect index 6e321ade..b72737be 100644 --- a/tests/PhpGenerator/expected/Extractor.bodies.expect +++ b/tests/PhpGenerator/expected/Extractor.bodies.expect @@ -90,13 +90,6 @@ abstract class Class7 comment */ // Alias Method will not be resolved in comment if ($member instanceof Method) { - $s1 = "\na\n\tb\n\t\tc\n"; - $s2 = "\na\n\t{$b}\n\t\t$c\n"; - - $s3 = "a\n\t{$b}\n\t\t$c" - ; - $s3 = "a\n\tb\n\t\tc" - ; // inline HTML is not supported ?> a diff --git a/tests/PhpGenerator/expected/Extractor.bodies.resolving.expect b/tests/PhpGenerator/expected/Extractor.bodies.resolving.expect index cb64aca7..e47b20fe 100644 --- a/tests/PhpGenerator/expected/Extractor.bodies.resolving.expect +++ b/tests/PhpGenerator/expected/Extractor.bodies.resolving.expect @@ -85,13 +85,6 @@ abstract class Class7 comment */ // Alias Method will not be resolved in comment if ($member instanceof \Abc\Method) { - $s1 = "\na\n\tb\n\t\tc\n"; - $s2 = "\na\n\t{$b}\n\t\t$c\n"; - - $s3 = "a\n\t{$b}\n\t\t$c" - ; - $s3 = "a\n\tb\n\t\tc" - ; // inline HTML is not supported ?> a diff --git a/tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect b/tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect index 6ae6f85c..63747419 100644 --- a/tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect +++ b/tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect @@ -85,13 +85,6 @@ abstract class Class7 comment */ // Alias Method will not be resolved in comment if ($member instanceof \Abc\Method) { - $s1 = "\na\n\tb\n\t\tc\n"; - $s2 = "\na\n\t{$b}\n\t\t$c\n"; - - $s3 = "a\n\t{$b}\n\t\t$c" - ; - $s3 = "a\n\tb\n\t\tc" - ; // inline HTML is not supported ?> a diff --git a/tests/PhpGenerator/expected/Extractor.expect b/tests/PhpGenerator/expected/Extractor.expect index 8b3afc30..3225cec4 100644 --- a/tests/PhpGenerator/expected/Extractor.expect +++ b/tests/PhpGenerator/expected/Extractor.expect @@ -21,7 +21,7 @@ class Class1 function comment2() { // comment - "bar"; + 'bar'; } diff --git a/tests/PhpGenerator/fixtures/bodies.php b/tests/PhpGenerator/fixtures/bodies.php index 99bc0a57..57133f2b 100644 --- a/tests/PhpGenerator/fixtures/bodies.php +++ b/tests/PhpGenerator/fixtures/bodies.php @@ -77,29 +77,6 @@ function complex() comment */ // Alias Method will not be resolved in comment if ($member instanceof Method) { - $s1 = ' -a - b - c -'; - $s2 = " -a - {$b} - $c -"; - - $s3 = << a From 0258dc13bcfcd3216940df4e31830dff9008077b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 17 Jan 2024 18:37:41 +0100 Subject: [PATCH 197/266] Extractor: fix escaping quotes when converting to double quoted string [Closes #151] --- src/PhpGenerator/Extractor.php | 2 +- tests/PhpGenerator/Extractor.strings.phpt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 73208b92..bff74eb3 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -132,7 +132,7 @@ private function prepareReplacements(array $nodes, int $level): array $replacements[] = [ $node->getStartFilePos() - $start, $node->getEndFilePos() - $start, - '"' . addcslashes($node->value, "\x00..\x1F") . '"', + '"' . addcslashes($node->value, "\x00..\x1F\"") . '"', ]; } elseif ( diff --git a/tests/PhpGenerator/Extractor.strings.phpt b/tests/PhpGenerator/Extractor.strings.phpt index ed561850..c091ce08 100644 --- a/tests/PhpGenerator/Extractor.strings.phpt +++ b/tests/PhpGenerator/Extractor.strings.phpt @@ -14,13 +14,13 @@ $extractor = new Extractor(<<<'XX' { $s = ' a - b + b"\' c '; $d = " a - b + b\"' c "; @@ -91,9 +91,9 @@ $extractor = new Extractor(<<<'XX' Assert::match( <<<'XX' - $s = "\na\n\tb\n\t\tc\n"; + $s = "\na\n\tb\"'\n\t\tc\n"; - $d = "\na\n\tb\n\t\tc\n"; + $d = "\na\n\tb\"'\n\t\tc\n"; $id = "\na\n\t{$b}\n\t\t$c\n"; XX, From 3eacc036ef442d1785a487edb2a66166c03df065 Mon Sep 17 00:00:00 2001 From: yoosef alipour Date: Wed, 17 Jan 2024 21:21:03 +0330 Subject: [PATCH 198/266] Extractor: supports PHP-Parser 5.0 (#150) --- composer.json | 2 +- src/PhpGenerator/Extractor.php | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 78827f66..b04fb925 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ }, "require-dev": { "nette/tester": "^2.4", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^4.18 || ^5.0", "tracy/tracy": "^2.8", "phpstan/phpstan": "^1.0", "jetbrains/phpstorm-attributes": "dev-master" diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index bff74eb3..6220cddd 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -47,8 +47,7 @@ private function parseCode(string $code): void } $this->code = Nette\Utils\Strings::normalizeNewlines($code); - $lexer = new PhpParser\Lexer\Emulative(['usedAttributes' => ['startFilePos', 'endFilePos', 'comments']]); - $parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7, $lexer); + $parser = (new ParserFactory)->createForNewestSupportedVersion(); $stmts = $parser->parse($this->code); $traverser = new PhpParser\NodeTraverser; From b74f58de3854890392e05a085eb513d10c64d865 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 18 Jan 2024 00:43:00 +0100 Subject: [PATCH 199/266] Dumper: removed support for obsolete Serializable --- src/PhpGenerator/Dumper.php | 3 --- tests/PhpGenerator/Dumper.dump().phpt | 23 ----------------------- 2 files changed, 26 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index a2dde21f..cc09c106 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -143,9 +143,6 @@ private function dumpObject(object $var, array $parents, int $level): string if (in_array($class, [\DateTime::class, \DateTimeImmutable::class], strict: true)) { return $this->format("new \\$class(?, new \\DateTimeZone(?))", $var->format('Y-m-d H:i:s.u'), $var->getTimeZone()->getName()); - } elseif ($var instanceof \Serializable) { - return 'unserialize(' . $this->dumpString(serialize($var)) . ')'; - } elseif ($var instanceof \UnitEnum) { return '\\' . $var::class . '::' . $var->name; diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index 29115522..5d3c292b 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -154,29 +154,6 @@ Assert::exception(function () { -// serializable -if (PHP_VERSION_ID < 80100) { - class Test3 implements Serializable - { - private $a; - - - public function serialize() - { - return ''; - } - - - public function unserialize($s) - { - } - } - - Assert::same('unserialize(\'C:5:"Test3":0:{}\')', $dumper->dump(new Test3)); - Assert::equal(new Test3, eval('return ' . $dumper->dump(new Test3) . ';')); -} - - // __serialize class TestSer { From 75c9ba313bde147eed9ceaac94dfe54a60b317fc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 18 Jan 2024 00:25:35 +0100 Subject: [PATCH 200/266] Dumper: refactoring, added dumpCustomObject() --- src/PhpGenerator/Dumper.php | 55 +++++++++++++------- tests/PhpGenerator/Dumper.dump().errors.phpt | 4 +- tests/PhpGenerator/Dumper.dump().phpt | 8 +-- tests/PhpGenerator/Dumper.dump().wrap.phpt | 2 +- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index cc09c106..24e929df 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -49,10 +49,10 @@ private function dumpVar(mixed &$var, array $parents = [], int $level = 0, int $ return $this->dumpLiteral($var, $level); } elseif (is_object($var)) { - return $this->dumpObject($var, $parents, $level); + return $this->dumpObject($var, $parents, $level, $column); } elseif (is_resource($var)) { - throw new Nette\InvalidArgumentException('Cannot dump resource.'); + throw new Nette\InvalidStateException('Cannot dump value of type resource.'); } else { return var_export($var, return: true); @@ -106,7 +106,7 @@ private function dumpArray(array &$var, array $parents, int $level, int $column) return '[]'; } elseif ($level > $this->maxDepth || in_array($var, $parents, strict: true)) { - throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.'); + throw new Nette\InvalidStateException('Nesting level too deep or recursive dependency.'); } $space = str_repeat($this->indentation, $level); @@ -136,12 +136,25 @@ private function dumpArray(array &$var, array $parents, int $level, int $column) /** @param array $parents */ - private function dumpObject(object $var, array $parents, int $level): string + private function dumpObject(object $var, array $parents, int $level, int $column): string { + if ($level > $this->maxDepth || in_array($var, $parents, strict: true)) { + throw new Nette\InvalidStateException('Nesting level too deep or recursive dependency.'); + } + $class = $var::class; + $parents[] = $var; - if (in_array($class, [\DateTime::class, \DateTimeImmutable::class], strict: true)) { - return $this->format("new \\$class(?, new \\DateTimeZone(?))", $var->format('Y-m-d H:i:s.u'), $var->getTimeZone()->getName()); + if ($class === \stdClass::class) { + $var = (array) $var; + return '(object) ' . $this->dumpArray($var, $parents, $level, $column + 10); + + } elseif ($class === \DateTime::class || $class === \DateTimeImmutable::class) { + return $this->format( + "new \\$class(?, new \\DateTimeZone(?))", + $var->format('Y-m-d H:i:s.u'), + $var->getTimeZone()->getName(), + ); } elseif ($var instanceof \UnitEnum) { return '\\' . $var::class . '::' . $var->name; @@ -154,15 +167,25 @@ private function dumpObject(object $var, array $parents, int $level): string : implode('::', (array) $inner) . '(...)'; } - throw new Nette\InvalidArgumentException('Cannot dump closure.'); + throw new Nette\InvalidStateException('Cannot dump object of type Closure.'); + + } else { + return $this->dumpCustomObject($var, $parents, $level); + } + } - } elseif ((new \ReflectionObject($var))->isAnonymous()) { - throw new Nette\InvalidArgumentException('Cannot dump anonymous class.'); - } elseif ($level > $this->maxDepth || in_array($var, $parents, strict: true)) { - throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.'); + /** @param array $parents */ + private function dumpCustomObject(object $var, array $parents, int $level): string + { + if ((new \ReflectionObject($var))->isAnonymous()) { + throw new Nette\InvalidStateException('Cannot dump an instance of an anonymous class.'); } + $class = $var::class; + $space = str_repeat($this->indentation, $level); + $out = "\n"; + if (method_exists($var, '__serialize')) { $arr = $var->__serialize(); } else { @@ -174,10 +197,6 @@ private function dumpObject(object $var, array $parents, int $level): string } } - $space = str_repeat($this->indentation, $level); - $out = "\n"; - $parents[] = $var; - foreach ($arr as $k => &$v) { if (!isset($props) || isset($props[$k])) { $out .= $space . $this->indentation @@ -187,11 +206,7 @@ private function dumpObject(object $var, array $parents, int $level): string } } - array_pop($parents); - $out .= $space; - return $class === \stdClass::class - ? "(object) [$out]" - : '\\' . self::class . "::createObject(\\$class::class, [$out])"; + return '\\' . self::class . "::createObject(\\$class::class, [$out$space])"; } diff --git a/tests/PhpGenerator/Dumper.dump().errors.phpt b/tests/PhpGenerator/Dumper.dump().errors.phpt index 722d50d8..f38820fe 100644 --- a/tests/PhpGenerator/Dumper.dump().errors.phpt +++ b/tests/PhpGenerator/Dumper.dump().errors.phpt @@ -17,7 +17,7 @@ Assert::exception(function () { $rec[] = &$rec; $dumper = new Dumper; $dumper->dump($rec); -}, Nette\InvalidArgumentException::class, 'Nesting level too deep or recursive dependency.'); +}, Nette\InvalidStateException::class, 'Nesting level too deep or recursive dependency.'); Assert::exception(function () { @@ -25,4 +25,4 @@ Assert::exception(function () { $rec->x = &$rec; $dumper = new Dumper; $dumper->dump($rec); -}, Nette\InvalidArgumentException::class, 'Nesting level too deep or recursive dependency.'); +}, Nette\InvalidStateException::class, 'Nesting level too deep or recursive dependency.'); diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index 5d3c292b..f9e1d507 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -70,12 +70,12 @@ Assert::same("[0 => 'a', -2 => 'b', 1 => 'c']", $dumper->dump(['a', -2 => 'b', 1 // stdClass Assert::same( - "(object) [\n\t'a' => 1,\n\t'b' => 2,\n]", + "(object) ['a' => 1, 'b' => 2]", $dumper->dump((object) ['a' => 1, 'b' => 2]), ); Assert::same( - "(object) [\n\t'a' => (object) [\n\t\t'b' => 2,\n\t],\n]", + "(object) ['a' => (object) ['b' => 2]]", $dumper->dump((object) ['a' => (object) ['b' => 2]]), ); @@ -128,7 +128,7 @@ Assert::exception(function () { $dumper = new Dumper; $dumper->dump(new class { }); -}, Nette\InvalidArgumentException::class, 'Cannot dump anonymous class.'); +}, Nette\InvalidStateException::class, 'Cannot dump an instance of an anonymous class.'); @@ -150,7 +150,7 @@ Assert::same( Assert::exception(function () { $dumper = new Dumper; $dumper->dump(function () {}); -}, Nette\InvalidArgumentException::class, 'Cannot dump closure.'); +}, Nette\InvalidStateException::class, 'Cannot dump object of type Closure.'); diff --git a/tests/PhpGenerator/Dumper.dump().wrap.phpt b/tests/PhpGenerator/Dumper.dump().wrap.phpt index d0e9ca12..35b0c870 100644 --- a/tests/PhpGenerator/Dumper.dump().wrap.phpt +++ b/tests/PhpGenerator/Dumper.dump().wrap.phpt @@ -13,7 +13,7 @@ require __DIR__ . '/../bootstrap.php'; $dumper = new Dumper; -$dumper->wrapLength = 21; +$dumper->wrapLength = 28; same( <<<'XX' [ From 08ab9bff22ae34fe4e1d2fe8ba16b3770ea2459f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 18 Jan 2024 00:53:16 +0100 Subject: [PATCH 201/266] Dumper: added $customObjects --- src/PhpGenerator/Dumper.php | 6 +++++- tests/PhpGenerator/Dumper.dump().phpt | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 24e929df..84d90a2d 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -22,6 +22,7 @@ final class Dumper public int $maxDepth = 50; public int $wrapLength = 120; public string $indentation = "\t"; + public bool $customObjects = true; /** @@ -169,8 +170,11 @@ private function dumpObject(object $var, array $parents, int $level, int $column throw new Nette\InvalidStateException('Cannot dump object of type Closure.'); - } else { + } elseif ($this->customObjects) { return $this->dumpCustomObject($var, $parents, $level); + + } else { + throw new Nette\InvalidStateException("Cannot dump object of type $class."); } } diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index f9e1d507..8dae1852 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -198,3 +198,13 @@ same( XX, $dumper->dump(new TestDateTime('2016-06-22 20:52:43.1234', new DateTimeZone('Europe/Prague'))), ); + + +// disallow custom objects +$dumper = new Dumper; +$dumper->customObjects = false; +Assert::exception( + fn() => $dumper->dump(new TestSer), + Nette\InvalidStateException::class, + 'Cannot dump object of type TestSer.', +); From c02c06591e53a365f0b4c6a377a1d4ea059db6e8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 8 Feb 2024 17:11:30 +0100 Subject: [PATCH 202/266] Factory: parameters 'int $foo = null' are parsed as '?int' --- src/PhpGenerator/Factory.php | 2 -- tests/PhpGenerator/expected/ClassType.from.bodies.expect | 2 +- tests/PhpGenerator/expected/ClassType.from.expect | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 9ab18d92..9509e763 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -218,8 +218,6 @@ public function fromParameterReflection(\ReflectionParameter $from): Parameter } else { $param->setDefaultValue($from->getDefaultValue()); } - - $param->setNullable($param->isNullable() && $param->getDefaultValue() !== null); } $param->setAttributes($this->getAttributes($from)); diff --git a/tests/PhpGenerator/expected/ClassType.from.bodies.expect b/tests/PhpGenerator/expected/ClassType.from.bodies.expect index 1a593c65..c33ef3d5 100644 --- a/tests/PhpGenerator/expected/ClassType.from.bodies.expect +++ b/tests/PhpGenerator/expected/ClassType.from.bodies.expect @@ -38,7 +38,7 @@ abstract class Class7 } - public function resolving($a = Abc\a\FOO, self $b = null, $c = self::FOO) + public function resolving($a = Abc\a\FOO, ?self $b = null, $c = self::FOO) { // constants echo FOO; diff --git a/tests/PhpGenerator/expected/ClassType.from.expect b/tests/PhpGenerator/expected/ClassType.from.expect index a6f42d2a..2ed08063 100644 --- a/tests/PhpGenerator/expected/ClassType.from.expect +++ b/tests/PhpGenerator/expected/ClassType.from.expect @@ -55,7 +55,7 @@ class Class2 extends Class1 implements Interface2 } - private function func4(array $a = [], Class2 $b = null, $c = Unknown::ABC) + private function func4(array $a = [], ?Class2 $b = null, $c = Unknown::ABC) { } @@ -77,7 +77,7 @@ class Class4 class Class5 { - public function func1(\A $a, ?\B $b, \C $c = null, \D $d = null, ?int $i = 1, ?array $arr = []) + public function func1(\A $a, ?\B $b, ?\C $c = null, ?\D $d = null, ?int $i = 1, ?array $arr = []) { } From 8674ce8b0a27dd165cbb3acb48e133149bccbd8d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 8 Feb 2024 17:45:33 +0100 Subject: [PATCH 203/266] added parameter $overwrite to addMember(), addMethod(), addProperty(), addConstant(), addCase() [Closes #152] --- src/PhpGenerator/ClassType.php | 4 ++-- src/PhpGenerator/EnumType.php | 8 ++++---- src/PhpGenerator/InterfaceType.php | 4 ++-- src/PhpGenerator/TraitType.php | 4 ++-- src/PhpGenerator/Traits/ConstantsAware.php | 4 ++-- src/PhpGenerator/Traits/MethodsAware.php | 4 ++-- src/PhpGenerator/Traits/PropertiesAware.php | 4 ++-- tests/PhpGenerator/ClassType.addMember.phpt | 3 +++ tests/PhpGenerator/ClassType.phpt | 15 +++++++++++++++ 9 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 2c2dfc0d..e6ababf6 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -177,7 +177,7 @@ public function removeImplement(string $name): static } - public function addMember(Method|Property|Constant|TraitUse $member): static + public function addMember(Method|Property|Constant|TraitUse $member, bool $overwrite = false): static { $name = $member->getName(); [$type, $n] = match (true) { @@ -186,7 +186,7 @@ public function addMember(Method|Property|Constant|TraitUse $member): static $member instanceof Property => ['properties', $name], $member instanceof TraitUse => ['traits', $name], }; - if (isset($this->$type[$n])) { + if (!$overwrite && isset($this->$type[$n])) { throw new Nette\InvalidStateException("Cannot add member '$name', because it already exists."); } $this->$type[$n] = $member; diff --git a/src/PhpGenerator/EnumType.php b/src/PhpGenerator/EnumType.php index 8122a2ad..cdf305c5 100644 --- a/src/PhpGenerator/EnumType.php +++ b/src/PhpGenerator/EnumType.php @@ -99,9 +99,9 @@ public function getCases(): array /** Adds case to enum */ - public function addCase(string $name, string|int|Literal|null $value = null): EnumCase + public function addCase(string $name, string|int|Literal|null $value = null, bool $overwrite = false): EnumCase { - if (isset($this->cases[$name])) { + if (!$overwrite && isset($this->cases[$name])) { throw new Nette\InvalidStateException("Cannot add cases '$name', because it already exists."); } return $this->cases[$name] = (new EnumCase($name)) @@ -116,7 +116,7 @@ public function removeCase(string $name): static } - public function addMember(Method|Constant|EnumCase|TraitUse $member): static + public function addMember(Method|Constant|EnumCase|TraitUse $member, bool $overwrite = false): static { $name = $member->getName(); [$type, $n] = match (true) { @@ -125,7 +125,7 @@ public function addMember(Method|Constant|EnumCase|TraitUse $member): static $member instanceof TraitUse => ['traits', $name], $member instanceof EnumCase => ['cases', $name], }; - if (isset($this->$type[$n])) { + if (!$overwrite && isset($this->$type[$n])) { throw new Nette\InvalidStateException("Cannot add member '$name', because it already exists."); } $this->$type[$n] = $member; diff --git a/src/PhpGenerator/InterfaceType.php b/src/PhpGenerator/InterfaceType.php index 86a7dd03..9353212f 100644 --- a/src/PhpGenerator/InterfaceType.php +++ b/src/PhpGenerator/InterfaceType.php @@ -51,14 +51,14 @@ public function addExtend(string $name): static } - public function addMember(Method|Constant $member): static + public function addMember(Method|Constant $member, bool $overwrite = false): static { $name = $member->getName(); [$type, $n] = match (true) { $member instanceof Constant => ['consts', $name], $member instanceof Method => ['methods', strtolower($name)], }; - if (isset($this->$type[$n])) { + if (!$overwrite && isset($this->$type[$n])) { throw new Nette\InvalidStateException("Cannot add member '$name', because it already exists."); } $this->$type[$n] = $member; diff --git a/src/PhpGenerator/TraitType.php b/src/PhpGenerator/TraitType.php index 43f2de30..792d32b3 100644 --- a/src/PhpGenerator/TraitType.php +++ b/src/PhpGenerator/TraitType.php @@ -22,7 +22,7 @@ final class TraitType extends ClassLike use Traits\PropertiesAware; use Traits\TraitsAware; - public function addMember(Method|Property|Constant|TraitUse $member): static + public function addMember(Method|Property|Constant|TraitUse $member, bool $overwrite = false): static { $name = $member->getName(); [$type, $n] = match (true) { @@ -31,7 +31,7 @@ public function addMember(Method|Property|Constant|TraitUse $member): static $member instanceof Property => ['properties', $name], $member instanceof TraitUse => ['traits', $name], }; - if (isset($this->$type[$n])) { + if (!$overwrite && isset($this->$type[$n])) { throw new Nette\InvalidStateException("Cannot add member '$name', because it already exists."); } $this->$type[$n] = $member; diff --git a/src/PhpGenerator/Traits/ConstantsAware.php b/src/PhpGenerator/Traits/ConstantsAware.php index 6cf1e2df..fad7e24b 100644 --- a/src/PhpGenerator/Traits/ConstantsAware.php +++ b/src/PhpGenerator/Traits/ConstantsAware.php @@ -48,9 +48,9 @@ public function getConstant(string $name): Constant } - public function addConstant(string $name, mixed $value): Constant + public function addConstant(string $name, mixed $value, bool $overwrite = false): Constant { - if (isset($this->consts[$name])) { + if (!$overwrite && isset($this->consts[$name])) { throw new Nette\InvalidStateException("Cannot add constant '$name', because it already exists."); } return $this->consts[$name] = (new Constant($name)) diff --git a/src/PhpGenerator/Traits/MethodsAware.php b/src/PhpGenerator/Traits/MethodsAware.php index a28c0613..5f8abe24 100644 --- a/src/PhpGenerator/Traits/MethodsAware.php +++ b/src/PhpGenerator/Traits/MethodsAware.php @@ -53,10 +53,10 @@ public function getMethod(string $name): Method } - public function addMethod(string $name): Method + public function addMethod(string $name, bool $overwrite = false): Method { $lower = strtolower($name); - if (isset($this->methods[$lower])) { + if (!$overwrite && isset($this->methods[$lower])) { throw new Nette\InvalidStateException("Cannot add method '$name', because it already exists."); } $method = new Method($name); diff --git a/src/PhpGenerator/Traits/PropertiesAware.php b/src/PhpGenerator/Traits/PropertiesAware.php index 2bc958cf..2c25cbdd 100644 --- a/src/PhpGenerator/Traits/PropertiesAware.php +++ b/src/PhpGenerator/Traits/PropertiesAware.php @@ -49,9 +49,9 @@ public function getProperty(string $name): Property /** @param string $name without $ */ - public function addProperty(string $name, mixed $value = null): Property + public function addProperty(string $name, mixed $value = null, bool $overwrite = false): Property { - if (isset($this->properties[$name])) { + if (!$overwrite && isset($this->properties[$name])) { throw new Nette\InvalidStateException("Cannot add property '$name', because it already exists."); } return $this->properties[$name] = func_num_args() > 1 diff --git a/tests/PhpGenerator/ClassType.addMember.phpt b/tests/PhpGenerator/ClassType.addMember.phpt index 95ee3654..9d561b78 100644 --- a/tests/PhpGenerator/ClassType.addMember.phpt +++ b/tests/PhpGenerator/ClassType.addMember.phpt @@ -29,3 +29,6 @@ Assert::exception( Nette\InvalidStateException::class, "Cannot add member 'FOO', because it already exists.", ); + +$class->addMember($new = new Nette\PhpGenerator\Method('FOO'), overwrite: true); +Assert::same($new, $class->getMethod('FOO')); diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index 8bf42361..9c5fac66 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -217,6 +217,21 @@ Assert::exception( ); +// overwrite +$class = new ClassType('Example'); +$class->addConstant('a', 1); +$new = $class->addConstant('a', 1, overwrite: true); +Assert::same($new, $class->getConstant('a')); + +$class->addProperty('a'); +$new = $class->addProperty('a', overwrite: true); +Assert::same($new, $class->getProperty('a')); + +$class->addMethod('a'); +$new = $class->addMethod('a', overwrite: true); +Assert::same($new, $class->getMethod('a')); + + // remove members $class = new ClassType('Example'); $class->addConstant('a', 1); From 4e7668ea0eb7814d8526a2cffb347f6789f65631 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 11 Feb 2024 20:21:05 +0100 Subject: [PATCH 204/266] Dumper: simplified list with negative keys is supported since PHP 8.0 https://3v4l.org/dvYQj --- src/PhpGenerator/Dumper.php | 8 +++----- tests/PhpGenerator/Dumper.dump().phpt | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 84d90a2d..820ac938 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -114,15 +114,13 @@ private function dumpArray(array &$var, array $parents, int $level, int $column) $outInline = ''; $outWrapped = "\n$space"; $parents[] = $var; - $counter = 0; - $hideKeys = is_int(($tmp = array_keys($var))[0]) && $tmp === range($tmp[0], $tmp[0] + count($var) - 1); + $hideKeys = is_int(($keys = array_keys($var))[0]) && $keys === range($keys[0], $keys[0] + count($var) - 1); foreach ($var as $k => &$v) { - $keyPart = $hideKeys && $k === $counter + $keyPart = $hideKeys && ($k !== $keys[0] || $k === 0) ? '' : $this->dumpVar($k) . ' => '; - $counter = is_int($k) ? max($k + 1, $counter) : $counter; - $outInline .= ($outInline === '' ? '' : ', ') . $keyPart; + $outInline .= ($k === $keys[0] ? '' : ', ') . $keyPart; $outInline .= $this->dumpVar($v, $parents, 0, $column + strlen($outInline)); $outWrapped .= $this->indentation . $keyPart diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index 8dae1852..379acbf6 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -63,7 +63,7 @@ Assert::same('[1, 2, 3]', $dumper->dump([1, 2, 3])); Assert::same("['a']", $dumper->dump(['a'])); Assert::same("[2 => 'a']", $dumper->dump([2 => 'a'])); Assert::same("[2 => 'a', 'b']", $dumper->dump([2 => 'a', 'b'])); -Assert::same("[-2 => 'a', -1 => 'b']", $dumper->dump([-2 => 'a', -1 => 'b'])); +Assert::same("[-2 => 'a', 'b']", $dumper->dump([-2 => 'a', -1 => 'b'])); Assert::same("[-2 => 'a', 0 => 'b']", $dumper->dump([-2 => 'a', 0 => 'b'])); Assert::same("[0 => 'a', -2 => 'b', 1 => 'c']", $dumper->dump(['a', -2 => 'b', 1 => 'c'])); From d281b08014979193fe4c6c718ce41849f7e8a2c2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 12 Feb 2024 12:52:56 +0100 Subject: [PATCH 205/266] Dumper::dumpArray(), dumpArguments() optimization --- src/PhpGenerator/Dumper.php | 41 +++++++++++++++---------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 820ac938..07b2513a 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -110,27 +110,22 @@ private function dumpArray(array &$var, array $parents, int $level, int $column) throw new Nette\InvalidStateException('Nesting level too deep or recursive dependency.'); } - $space = str_repeat($this->indentation, $level); - $outInline = ''; - $outWrapped = "\n$space"; $parents[] = $var; $hideKeys = is_int(($keys = array_keys($var))[0]) && $keys === range($keys[0], $keys[0] + count($var) - 1); + $pairs = []; foreach ($var as $k => &$v) { $keyPart = $hideKeys && ($k !== $keys[0] || $k === 0) ? '' : $this->dumpVar($k) . ' => '; - $outInline .= ($k === $keys[0] ? '' : ', ') . $keyPart; - $outInline .= $this->dumpVar($v, $parents, 0, $column + strlen($outInline)); - $outWrapped .= $this->indentation - . $keyPart - . $this->dumpVar($v, $parents, $level + 1, strlen($keyPart)) - . ",\n$space"; + $pairs[] = $keyPart . $this->dumpVar($v, $parents, $level + 1, strlen($keyPart) + 1); // 1 = comma after item } - array_pop($parents); - $wrap = str_contains($outInline, "\n") || $level * self::IndentLength + $column + strlen($outInline) + 3 > $this->wrapLength; // 3 = [], - return '[' . ($wrap ? $outWrapped : $outInline) . ']'; + $line = '[' . implode(', ', $pairs) . ']'; + $space = str_repeat($this->indentation, $level); + return !str_contains($line, "\n") && $level * self::IndentLength + $column + strlen($line) <= $this->wrapLength + ? $line + : "[\n$space" . $this->indentation . implode(",\n$space" . $this->indentation, $pairs) . ",\n$space]"; } @@ -263,21 +258,19 @@ public function format(string $statement, mixed ...$args): string } - /** @param mixed[] $var */ - private function dumpArguments(array &$var, int $column, bool $named): string + /** @param mixed[] $args */ + private function dumpArguments(array $args, int $column, bool $named): string { - $outInline = $outWrapped = ''; - - foreach ($var as $k => &$v) { - $k = !$named || is_int($k) ? '' : $k . ': '; - $outInline .= $outInline === '' ? '' : ', '; - $outInline .= $k . $this->dumpVar($v, [$var], 0, $column + strlen($outInline)); - $outWrapped .= "\n" . $this->indentation . $k . $this->dumpVar($v, [$var], 1) . ','; + $pairs = []; + foreach ($args as $k => $v) { + $name = $named && !is_int($k) ? $k . ': ' : ''; + $pairs[] = $name . $this->dumpVar($v, [$args], 0, $column + strlen($name) + 1); // 1 = ) after args } - return count($var) > 1 && (str_contains($outInline, "\n") || $column + strlen($outInline) > $this->wrapLength) - ? $outWrapped . "\n" - : $outInline; + $line = implode(', ', $pairs); + return count($args) < 2 || (!str_contains($line, "\n") && $column + strlen($line) <= $this->wrapLength) + ? $line + : "\n" . $this->indentation . implode(",\n" . $this->indentation, $pairs) . ",\n"; } From dec07114301566fe171bd7ef113171b428724fe3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 11 Feb 2024 20:58:26 +0100 Subject: [PATCH 206/266] Dumper: refactoring --- src/PhpGenerator/Dumper.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 07b2513a..da276bea 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -13,7 +13,7 @@ /** - * PHP code generator utils. + * Generates a PHP representation of a variable. */ final class Dumper { @@ -35,7 +35,7 @@ public function dump(mixed $var, int $column = 0): string /** @param array $parents */ - private function dumpVar(mixed &$var, array $parents = [], int $level = 0, int $column = 0): string + private function dumpVar(mixed $var, array $parents = [], int $level = 0, int $column = 0): string { if ($var === null) { return 'null'; @@ -101,7 +101,7 @@ private static function utf8Ord(string $c): int * @param mixed[] $var * @param array $parents */ - private function dumpArray(array &$var, array $parents, int $level, int $column): string + private function dumpArray(array $var, array $parents, int $level, int $column): string { if (empty($var)) { return '[]'; @@ -114,7 +114,7 @@ private function dumpArray(array &$var, array $parents, int $level, int $column) $hideKeys = is_int(($keys = array_keys($var))[0]) && $keys === range($keys[0], $keys[0] + count($var) - 1); $pairs = []; - foreach ($var as $k => &$v) { + foreach ($var as $k => $v) { $keyPart = $hideKeys && ($k !== $keys[0] || $k === 0) ? '' : $this->dumpVar($k) . ' => '; @@ -134,6 +134,8 @@ private function dumpObject(object $var, array $parents, int $level, int $column { if ($level > $this->maxDepth || in_array($var, $parents, strict: true)) { throw new Nette\InvalidStateException('Nesting level too deep or recursive dependency.'); + } elseif ((new \ReflectionObject($var))->isAnonymous()) { + throw new Nette\InvalidStateException('Cannot dump an instance of an anonymous class.'); } $class = $var::class; @@ -175,10 +177,6 @@ private function dumpObject(object $var, array $parents, int $level, int $column /** @param array $parents */ private function dumpCustomObject(object $var, array $parents, int $level): string { - if ((new \ReflectionObject($var))->isAnonymous()) { - throw new Nette\InvalidStateException('Cannot dump an instance of an anonymous class.'); - } - $class = $var::class; $space = str_repeat($this->indentation, $level); $out = "\n"; @@ -194,7 +192,7 @@ private function dumpCustomObject(object $var, array $parents, int $level): stri } } - foreach ($arr as $k => &$v) { + foreach ($arr as $k => $v) { if (!isset($props) || isset($props[$k])) { $out .= $space . $this->indentation . ($keyPart = $this->dumpVar($k) . ' => ') From af74f21170bbcd42eb0b544fbb6c02e487764158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 7 Mar 2024 23:12:41 +0100 Subject: [PATCH 207/266] Make `ClassLike::from` return type assert the subclass type (BC break) (#154) --- src/PhpGenerator/ClassLike.php | 17 +++++++-- tests/PhpGenerator/ClassLike.typecheck.phpt | 36 ++++++++++++++++++++ tests/PhpGenerator/ClassType.from.82.phpt | 3 +- tests/PhpGenerator/ClassType.from.phpt | 9 ++--- tests/PhpGenerator/ClassType.from.trait.phpt | 6 ++-- 5 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 tests/PhpGenerator/ClassLike.typecheck.phpt diff --git a/src/PhpGenerator/ClassLike.php b/src/PhpGenerator/ClassLike.php index ce49782d..15c2d30c 100644 --- a/src/PhpGenerator/ClassLike.php +++ b/src/PhpGenerator/ClassLike.php @@ -40,15 +40,28 @@ abstract class ClassLike public static function from(string|object $class, bool $withBodies = false): self { - return (new Factory) + $instance = (new Factory) ->fromClassReflection(new \ReflectionClass($class), $withBodies); + + if (!$instance instanceof static) { + $class = is_object($class) ? $class::class : $class; + trigger_error("$class cannot be represented with " . static::class . '. Call ' . $instance::class . '::' . __FUNCTION__ . '() or ' . __METHOD__ . '() instead.', E_USER_WARNING); + } + + return $instance; } public static function fromCode(string $code): self { - return (new Factory) + $instance = (new Factory) ->fromClassCode($code); + + if (!$instance instanceof static) { + trigger_error('Provided code cannot be represented with ' . static::class . '. Call ' . $instance::class . '::' . __FUNCTION__ . '() or ' . __METHOD__ . '() instead.', E_USER_WARNING); + } + + return $instance; } diff --git a/tests/PhpGenerator/ClassLike.typecheck.phpt b/tests/PhpGenerator/ClassLike.typecheck.phpt new file mode 100644 index 00000000..9b681c80 --- /dev/null +++ b/tests/PhpGenerator/ClassLike.typecheck.phpt @@ -0,0 +1,36 @@ + ClassType::from(Abc\Interface1::class), + E_USER_WARNING, + 'Abc\Interface1 cannot be represented with Nette\PhpGenerator\ClassType. Call Nette\PhpGenerator\InterfaceType::from() or Nette\PhpGenerator\ClassLike::from() instead.', +); + +Assert::error( + fn() => TraitType::from(Abc\Class1::class), + E_USER_WARNING, + 'Abc\Class1 cannot be represented with Nette\PhpGenerator\TraitType. Call Nette\PhpGenerator\ClassType::from() or Nette\PhpGenerator\ClassLike::from() instead.', +); + +Assert::error( + fn() => ClassType::fromCode(' InterfaceType::fromCode(' ClassType::from($class), $classes); +$res = array_map(fn($class) => ClassLike::from($class), $classes); sameFile(__DIR__ . '/expected/ClassType.from.trait-use.expect', implode("\n", $res)); -$res = array_map(fn($class) => ClassType::from($class, withBodies: true), $classes); +$res = array_map(fn($class) => ClassLike::from($class, withBodies: true), $classes); sameFile(__DIR__ . '/expected/ClassType.from.trait-use.bodies.expect', implode("\n", $res)); From b135071d8da108445e4df2fc6a75522b23c0237d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 7 Mar 2024 23:52:24 +0100 Subject: [PATCH 208/266] PsrPrinter: opening bracket on the correct line [Closes #155] --- src/PhpGenerator/Printer.php | 10 ++++++++-- src/PhpGenerator/PsrPrinter.php | 6 ++++++ tests/PhpGenerator/PsrPrinter.phpt | 5 ++++- tests/PhpGenerator/expected/PsrPrinter.class.expect | 6 ++++-- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 8a17f049..1d9528ae 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -48,7 +48,7 @@ public function printFunction(GlobalFunction $function, ?PhpNamespace $namespace $params = $this->printParameters($function, strlen($line) + strlen($returnType) + 2); // 2 = parentheses $body = Helpers::simplifyTaggedNames($function->getBody(), $this->namespace); $body = ltrim(rtrim(Strings::normalize($body)) . "\n"); - $braceOnNextLine = $this->bracesOnNextLine && (!str_contains($params, "\n") || $returnType); + $braceOnNextLine = $this->isBraceOnNextLine(str_contains($params, "\n"), (bool) $returnType); return $this->printDocComment($function) . $this->printAttributes($function->getAttributes()) @@ -119,7 +119,7 @@ public function printMethod(Method $method, ?PhpNamespace $namespace = null, boo $params = $this->printParameters($method, strlen($line) + strlen($returnType) + strlen($this->indentation) + 2); $body = Helpers::simplifyTaggedNames($method->getBody(), $this->namespace); $body = ltrim(rtrim(Strings::normalize($body)) . "\n"); - $braceOnNextLine = $this->bracesOnNextLine && (!str_contains($params, "\n") || $returnType); + $braceOnNextLine = $this->isBraceOnNextLine(str_contains($params, "\n"), (bool) $returnType); return $this->printDocComment($method) . $this->printAttributes($method->getAttributes()) @@ -466,4 +466,10 @@ private function joinProperties(array $props): string ? implode(str_repeat("\n", $this->linesBetweenProperties), $props) : preg_replace('#^(\w.*\n)\n(?=\w.*;)#m', '$1', implode("\n", $props)); } + + + protected function isBraceOnNextLine(bool $multiLine, bool $hasReturnType): bool + { + return $this->bracesOnNextLine && (!$multiLine || $hasReturnType); + } } diff --git a/src/PhpGenerator/PsrPrinter.php b/src/PhpGenerator/PsrPrinter.php index ab62c174..f1b3b37b 100644 --- a/src/PhpGenerator/PsrPrinter.php +++ b/src/PhpGenerator/PsrPrinter.php @@ -18,4 +18,10 @@ final class PsrPrinter extends Printer public string $indentation = ' '; public int $linesBetweenMethods = 1; public int $linesBetweenUseTypes = 1; + + + protected function isBraceOnNextLine(bool $multiLine, bool $hasReturnType): bool + { + return !$multiLine; + } } diff --git a/tests/PhpGenerator/PsrPrinter.phpt b/tests/PhpGenerator/PsrPrinter.phpt index a8040bb0..a11e16ef 100644 --- a/tests/PhpGenerator/PsrPrinter.phpt +++ b/tests/PhpGenerator/PsrPrinter.phpt @@ -47,7 +47,10 @@ $class->addMethod('first') ->addParameter('var') ->setType('stdClass'); -$class->addMethod('second'); +$class->addMethod('braces1') + ->setReturnType('stdClass') + ->addParameter('var') + ->addAttribute('attr'); sameFile(__DIR__ . '/expected/PsrPrinter.class.expect', $printer->printClass($class)); diff --git a/tests/PhpGenerator/expected/PsrPrinter.class.expect b/tests/PhpGenerator/expected/PsrPrinter.class.expect index 5b57db98..f4e5e464 100644 --- a/tests/PhpGenerator/expected/PsrPrinter.class.expect +++ b/tests/PhpGenerator/expected/PsrPrinter.class.expect @@ -54,7 +54,9 @@ final class Example extends ParentClass implements IExample ]; } - public function second() - { + public function braces1( + #[attr] + $var, + ): stdClass { } } From 4e0c6b4ae67e0c684cbe0b64c27b4c4dd019e8f2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 2 May 2024 12:58:42 +0200 Subject: [PATCH 209/266] github actions updated --- .github/workflows/coding-style.yml | 4 ++-- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index 875287f8..97e6c166 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -7,7 +7,7 @@ jobs: name: Nette Code Checker runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: 8.0 @@ -21,7 +21,7 @@ jobs: name: Nette Coding Standard runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: 8.0 diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 25e44dd0..1b855cdb 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@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: 8.0 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 58b8cf7c..1189b342 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@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -32,7 +32,7 @@ jobs: name: Lowest Dependencies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - 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@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: 8.0 From 8a9c4077d320f6bc378d090a99a826521753cee9 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 May 2024 16:35:24 +0200 Subject: [PATCH 210/266] cs --- src/PhpGenerator/Dumper.php | 2 +- src/PhpGenerator/Factory.php | 2 +- src/PhpGenerator/PhpFile.php | 4 ++-- src/PhpGenerator/Traits/ConstantsAware.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index da276bea..b6047067 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -75,7 +75,7 @@ private function dumpString(string $s): string $escaped = preg_replace_callback( $utf8 ? '#[\p{C}\\\\]#u' : '#[\x00-\x1F\x7F-\xFF\\\\]#', fn($m) => $special[$m[0]] ?? (strlen($m[0]) === 1 - ? '\x' . str_pad(strtoupper(dechex(ord($m[0]))), 2, '0', STR_PAD_LEFT) . '' + ? '\x' . str_pad(strtoupper(dechex(ord($m[0]))), 2, '0', STR_PAD_LEFT) : '\u{' . strtoupper(ltrim(dechex(self::utf8Ord($m[0])), '0')) . '}'), $s, ); diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 9509e763..544e3f35 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -230,7 +230,7 @@ public function fromConstantReflection(\ReflectionClassConstant $from): Constant $const = new Constant($from->name); $const->setValue($from->getValue()); $const->setVisibility($this->getVisibility($from)); - $const->setFinal(PHP_VERSION_ID >= 80100 ? $from->isFinal() : false); + $const->setFinal(PHP_VERSION_ID >= 80100 && $from->isFinal()); $const->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); $const->setAttributes($this->getAttributes($from)); return $const; diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index e8f0bc9b..bbdf8e8f 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -134,9 +134,9 @@ public function addUse(string $name, ?string $alias = null, string $of = PhpName /** * Adds declare(strict_types=1) to output. */ - public function setStrictTypes(bool $on = true): static + public function setStrictTypes(bool $state = true): static { - $this->strictTypes = $on; + $this->strictTypes = $state; return $this; } diff --git a/src/PhpGenerator/Traits/ConstantsAware.php b/src/PhpGenerator/Traits/ConstantsAware.php index fad7e24b..468a1cbd 100644 --- a/src/PhpGenerator/Traits/ConstantsAware.php +++ b/src/PhpGenerator/Traits/ConstantsAware.php @@ -27,7 +27,7 @@ public function setConstants(array $consts): static { (function (Constant ...$consts) {})(...$consts); $this->consts = []; - foreach ($consts as $k => $const) { + foreach ($consts as $const) { $this->consts[$const->getName()] = $const; } From 48c7cdf66d15d98782234b3543ad04184313fdc4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 21 Apr 2024 16:00:39 +0200 Subject: [PATCH 211/266] added phpDoc comments --- src/PhpGenerator/Closure.php | 1 + src/PhpGenerator/EnumType.php | 3 ++ src/PhpGenerator/InterfaceType.php | 3 ++ src/PhpGenerator/PhpFile.php | 42 +++++++++++++---- src/PhpGenerator/PhpNamespace.php | 50 ++++++++++++++++++++- src/PhpGenerator/TraitType.php | 3 ++ src/PhpGenerator/Traits/AttributeAware.php | 1 + src/PhpGenerator/Traits/CommentAware.php | 3 ++ src/PhpGenerator/Traits/ConstantsAware.php | 8 +++- src/PhpGenerator/Traits/FunctionLike.php | 1 + src/PhpGenerator/Traits/MethodsAware.php | 8 +++- src/PhpGenerator/Traits/PropertiesAware.php | 10 ++++- src/PhpGenerator/Traits/TraitsAware.php | 8 +++- 13 files changed, 126 insertions(+), 15 deletions(-) diff --git a/src/PhpGenerator/Closure.php b/src/PhpGenerator/Closure.php index 4f1fa154..e958a886 100644 --- a/src/PhpGenerator/Closure.php +++ b/src/PhpGenerator/Closure.php @@ -35,6 +35,7 @@ public function __toString(): string /** + * Replaces all uses. * @param Parameter[] $uses */ public function setUses(array $uses): static diff --git a/src/PhpGenerator/EnumType.php b/src/PhpGenerator/EnumType.php index cdf305c5..75b903bf 100644 --- a/src/PhpGenerator/EnumType.php +++ b/src/PhpGenerator/EnumType.php @@ -116,6 +116,9 @@ public function removeCase(string $name): static } + /** + * Adds a member. If it already exists, throws an exception or overwrites it if $overwrite is true. + */ public function addMember(Method|Constant|EnumCase|TraitUse $member, bool $overwrite = false): static { $name = $member->getName(); diff --git a/src/PhpGenerator/InterfaceType.php b/src/PhpGenerator/InterfaceType.php index 9353212f..ac082e0a 100644 --- a/src/PhpGenerator/InterfaceType.php +++ b/src/PhpGenerator/InterfaceType.php @@ -51,6 +51,9 @@ public function addExtend(string $name): static } + /** + * Adds a member. If it already exists, throws an exception or overwrites it if $overwrite is true. + */ public function addMember(Method|Constant $member, bool $overwrite = false): static { $name = $member->getName(); diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index bbdf8e8f..87ce3b33 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -33,6 +33,10 @@ public static function fromCode(string $code): self } + /** + * Adds a class to the file. If it already exists, throws an exception. + * As a parameter, pass the full name with namespace. + */ public function addClass(string $name): ClassType { return $this @@ -41,6 +45,10 @@ public function addClass(string $name): ClassType } + /** + * Adds an interface to the file. If it already exists, throws an exception. + * As a parameter, pass the full name with namespace. + */ public function addInterface(string $name): InterfaceType { return $this @@ -49,6 +57,10 @@ public function addInterface(string $name): InterfaceType } + /** + * Adds a trait to the file. If it already exists, throws an exception. + * As a parameter, pass the full name with namespace. + */ public function addTrait(string $name): TraitType { return $this @@ -57,6 +69,10 @@ public function addTrait(string $name): TraitType } + /** + * Adds an enum to the file. If it already exists, throws an exception. + * As a parameter, pass the full name with namespace. + */ public function addEnum(string $name): EnumType { return $this @@ -65,6 +81,21 @@ public function addEnum(string $name): EnumType } + /** + * Adds a function to the file. If it already exists, throws an exception. + * As a parameter, pass the full name with namespace. + */ + public function addFunction(string $name): GlobalFunction + { + return $this + ->addNamespace(Helpers::extractNamespace($name)) + ->addFunction(Helpers::extractShortName($name)); + } + + + /** + * Adds a namespace to the file. If it already exists, it returns the existing one. + */ public function addNamespace(string|PhpNamespace $namespace): PhpNamespace { $res = $namespace instanceof PhpNamespace @@ -79,14 +110,6 @@ public function addNamespace(string|PhpNamespace $namespace): PhpNamespace } - public function addFunction(string $name): GlobalFunction - { - return $this - ->addNamespace(Helpers::extractNamespace($name)) - ->addFunction(Helpers::extractShortName($name)); - } - - /** @return PhpNamespace[] */ public function getNamespaces(): array { @@ -124,6 +147,9 @@ public function getFunctions(): array } + /** + * Adds a use statement to the file, to the global namespace. + */ public function addUse(string $name, ?string $alias = null, string $of = PhpNamespace::NameNormal): static { $this->addNamespace('')->addUse($name, $alias, $of); diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index b930e8b1..b0e3c1a9 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -88,6 +88,7 @@ public function hasBracketedSyntax(): bool /** + * Adds a use statement to the namespace for class, function or constant. * @throws InvalidStateException */ public function addUse(string $name, ?string $alias = null, string $of = self::NameNormal): static @@ -140,12 +141,18 @@ public function removeUse(string $name, string $of = self::NameNormal): void } + /** + * Adds a use statement to the namespace for function. + */ public function addUseFunction(string $name, ?string $alias = null): static { return $this->addUse($name, $alias, self::NameFunction); } + /** + * Adds a use statement to the namespace for constant. + */ public function addUseConstant(string $name, ?string $alias = null): static { return $this->addUse($name, $alias, self::NameConstant); @@ -164,6 +171,9 @@ public function getUses(string $of = self::NameNormal): array } + /** + * Resolves relative name to full name. + */ public function resolveName(string $name, string $of = self::NameNormal): string { if (isset(Helpers::Keywords[strtolower($name)]) || $name === '') { @@ -185,12 +195,18 @@ public function resolveName(string $name, string $of = self::NameNormal): string } + /** + * Simplifies type hint with relative names. + */ public function simplifyType(string $type, string $of = self::NameNormal): string { return preg_replace_callback('~[\w\x7f-\xff\\\\]+~', fn($m) => $this->simplifyName($m[0], $of), $type); } + /** + * Simplifies the full name of a class, function, or constant to a relative name. + */ public function simplifyName(string $name, string $of = self::NameNormal): string { if (isset(Helpers::Keywords[strtolower($name)]) || $name === '') { @@ -235,6 +251,9 @@ public function simplifyName(string $name, string $of = self::NameNormal): strin } + /** + * Adds a class-like type to the namespace. If it already exists, throws an exception. + */ public function add(ClassType|InterfaceType|TraitType|EnumType $class): static { $name = $class->getName(); @@ -254,6 +273,9 @@ public function add(ClassType|InterfaceType|TraitType|EnumType $class): static } + /** + * Adds a class to the namespace. If it already exists, throws an exception. + */ public function addClass(string $name): ClassType { $this->add($class = new ClassType($name, $this)); @@ -261,6 +283,9 @@ public function addClass(string $name): ClassType } + /** + * Adds an interface to the namespace. If it already exists, throws an exception. + */ public function addInterface(string $name): InterfaceType { $this->add($iface = new InterfaceType($name, $this)); @@ -268,6 +293,9 @@ public function addInterface(string $name): InterfaceType } + /** + * Adds a trait to the namespace. If it already exists, throws an exception. + */ public function addTrait(string $name): TraitType { $this->add($trait = new TraitType($name, $this)); @@ -275,6 +303,9 @@ public function addTrait(string $name): TraitType } + /** + * Adds an enum to the namespace. If it already exists, throws an exception. + */ public function addEnum(string $name): EnumType { $this->add($enum = new EnumType($name, $this)); @@ -282,6 +313,9 @@ public function addEnum(string $name): EnumType } + /** + * Removes a class-like type from namespace. + */ public function removeClass(string $name): static { unset($this->classes[strtolower($name)]); @@ -289,6 +323,9 @@ public function removeClass(string $name): static } + /** + * Adds a function to the namespace. If it already exists, throws an exception. + */ public function addFunction(string $name): GlobalFunction { $lower = strtolower($name); @@ -302,6 +339,9 @@ public function addFunction(string $name): GlobalFunction } + /** + * Removes a function type from namespace. + */ public function removeFunction(string $name): static { unset($this->functions[strtolower($name)]); @@ -309,7 +349,10 @@ public function removeFunction(string $name): static } - /** @return (ClassType|InterfaceType|TraitType|EnumType)[] */ + /** + * Returns all class-like types in the namespace. + * @return (ClassType|InterfaceType|TraitType|EnumType)[] + */ public function getClasses(): array { $res = []; @@ -321,7 +364,10 @@ public function getClasses(): array } - /** @return GlobalFunction[] */ + /** + * Returns all functions in the namespace. + * @return GlobalFunction[] + */ public function getFunctions(): array { $res = []; diff --git a/src/PhpGenerator/TraitType.php b/src/PhpGenerator/TraitType.php index 792d32b3..9c7b78b5 100644 --- a/src/PhpGenerator/TraitType.php +++ b/src/PhpGenerator/TraitType.php @@ -22,6 +22,9 @@ final class TraitType extends ClassLike use Traits\PropertiesAware; use Traits\TraitsAware; + /** + * Adds a member. If it already exists, throws an exception or overwrites it if $overwrite is true. + */ public function addMember(Method|Property|Constant|TraitUse $member, bool $overwrite = false): static { $name = $member->getName(); diff --git a/src/PhpGenerator/Traits/AttributeAware.php b/src/PhpGenerator/Traits/AttributeAware.php index 09cad7ce..9e9d7d44 100644 --- a/src/PhpGenerator/Traits/AttributeAware.php +++ b/src/PhpGenerator/Traits/AttributeAware.php @@ -30,6 +30,7 @@ public function addAttribute(string $name, array $args = []): static /** + * Replaces all attributes. * @param Attribute[] $attrs */ public function setAttributes(array $attrs): static diff --git a/src/PhpGenerator/Traits/CommentAware.php b/src/PhpGenerator/Traits/CommentAware.php index 85d380b2..38879587 100644 --- a/src/PhpGenerator/Traits/CommentAware.php +++ b/src/PhpGenerator/Traits/CommentAware.php @@ -31,6 +31,9 @@ public function getComment(): ?string } + /** + * Adds a new line to the comment. + */ public function addComment(string $val): static { $this->comment .= $this->comment ? "\n$val" : $val; diff --git a/src/PhpGenerator/Traits/ConstantsAware.php b/src/PhpGenerator/Traits/ConstantsAware.php index 468a1cbd..58a9093d 100644 --- a/src/PhpGenerator/Traits/ConstantsAware.php +++ b/src/PhpGenerator/Traits/ConstantsAware.php @@ -22,7 +22,10 @@ trait ConstantsAware private array $consts = []; - /** @param Constant[] $consts */ + /** + * Replaces all constants. + * @param Constant[] $consts + */ public function setConstants(array $consts): static { (function (Constant ...$consts) {})(...$consts); @@ -48,6 +51,9 @@ public function getConstant(string $name): Constant } + /** + * Adds a constant. If it already exists, throws an exception or overwrites it if $overwrite is true. + */ public function addConstant(string $name, mixed $value, bool $overwrite = false): Constant { if (!$overwrite && isset($this->consts[$name])) { diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index 05e039f8..8389ed26 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -92,6 +92,7 @@ public function getParameter(string $name): Parameter /** + * Adds a parameter. If it already exists, it overwrites it. * @param string $name without $ */ public function addParameter(string $name, mixed $defaultValue = null): Parameter diff --git a/src/PhpGenerator/Traits/MethodsAware.php b/src/PhpGenerator/Traits/MethodsAware.php index 5f8abe24..f6f36534 100644 --- a/src/PhpGenerator/Traits/MethodsAware.php +++ b/src/PhpGenerator/Traits/MethodsAware.php @@ -22,7 +22,10 @@ trait MethodsAware private array $methods = []; - /** @param Method[] $methods */ + /** + * Replaces all methods. + * @param Method[] $methods + */ public function setMethods(array $methods): static { (function (Method ...$methods) {})(...$methods); @@ -53,6 +56,9 @@ public function getMethod(string $name): Method } + /** + * Adds a method. If it already exists, throws an exception or overwrites it if $overwrite is true. + */ public function addMethod(string $name, bool $overwrite = false): Method { $lower = strtolower($name); diff --git a/src/PhpGenerator/Traits/PropertiesAware.php b/src/PhpGenerator/Traits/PropertiesAware.php index 2c25cbdd..a87ab4d8 100644 --- a/src/PhpGenerator/Traits/PropertiesAware.php +++ b/src/PhpGenerator/Traits/PropertiesAware.php @@ -22,7 +22,10 @@ trait PropertiesAware private array $properties = []; - /** @param Property[] $props */ + /** + * Replaces all properties. + * @param Property[] $props + */ public function setProperties(array $props): static { (function (Property ...$props) {})(...$props); @@ -48,7 +51,10 @@ public function getProperty(string $name): Property } - /** @param string $name without $ */ + /** + * Adds a property. If it already exists, throws an exception or overwrites it if $overwrite is true. + * @param string $name without $ + */ public function addProperty(string $name, mixed $value = null, bool $overwrite = false): Property { if (!$overwrite && isset($this->properties[$name])) { diff --git a/src/PhpGenerator/Traits/TraitsAware.php b/src/PhpGenerator/Traits/TraitsAware.php index 25d0cd21..adf2ad4f 100644 --- a/src/PhpGenerator/Traits/TraitsAware.php +++ b/src/PhpGenerator/Traits/TraitsAware.php @@ -22,7 +22,10 @@ trait TraitsAware private array $traits = []; - /** @param TraitUse[] $traits */ + /** + * Replaces all traits. + * @param TraitUse[] $traits + */ public function setTraits(array $traits): static { (function (TraitUse ...$traits) {})(...$traits); @@ -42,6 +45,9 @@ public function getTraits(): array } + /** + * Adds a method. If it already exists, throws an exception. + */ public function addTrait(string $name): TraitUse { if (isset($this->traits[$name])) { From 4d8a95a250d4fae2e3814eaa9772f8b33c5dc414 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 25 Apr 2024 15:22:44 +0200 Subject: [PATCH 212/266] added PhpFile::removeNamespace() --- src/PhpGenerator/PhpFile.php | 11 +++++++++++ tests/PhpGenerator/PhpFile.phpt | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index 87ce3b33..76b7947d 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -110,6 +110,17 @@ public function addNamespace(string|PhpNamespace $namespace): PhpNamespace } + /** + * Removes the namespace from the file. + */ + public function removeNamespace(string|PhpNamespace $namespace): static + { + $name = $namespace instanceof PhpNamespace ? $namespace->getName() : $namespace; + unset($this->namespaces[$name]); + return $this; + } + + /** @return PhpNamespace[] */ public function getNamespaces(): array { diff --git a/tests/PhpGenerator/PhpFile.phpt b/tests/PhpGenerator/PhpFile.phpt index 9142aa45..04a024d9 100644 --- a/tests/PhpGenerator/PhpFile.phpt +++ b/tests/PhpGenerator/PhpFile.phpt @@ -18,6 +18,10 @@ $file->addComment('This file is auto-generated. DO NOT EDIT!'); $file->addComment('Hey there, I\'m here to document things.'); +$namespace = $file->addNamespace('Deleted'); +$namespace->addClass('Foo'); +$file->removeNamespace('Deleted'); + $namespaceFoo = $file->addNamespace('Foo'); $classA = $namespaceFoo->addClass('A'); From b5a3f3b049fc1b9769ed4307637875eeccbb7b15 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 28 Apr 2024 16:13:04 +0200 Subject: [PATCH 213/266] added CommentAware::removeComment() --- src/PhpGenerator/Traits/CommentAware.php | 7 +++++++ tests/PhpGenerator/ClassType.phpt | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/PhpGenerator/Traits/CommentAware.php b/src/PhpGenerator/Traits/CommentAware.php index 38879587..deff6a5a 100644 --- a/src/PhpGenerator/Traits/CommentAware.php +++ b/src/PhpGenerator/Traits/CommentAware.php @@ -39,4 +39,11 @@ public function addComment(string $val): static $this->comment .= $this->comment ? "\n$val" : $val; return $this; } + + + public function removeComment(): static + { + $this->comment = null; + return $this; + } } diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index 9c5fac66..a8e81eee 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -59,7 +59,9 @@ $class->addProperty('handle') ->addComment('@var resource orignal file handle'); $class->addProperty('order') - ->setValue(new Literal('RecursiveIteratorIterator::SELF_FIRST')); + ->setValue(new Literal('RecursiveIteratorIterator::SELF_FIRST')) + ->addComment('foo') + ->removeComment(); $class->addProperty('typed1') ->setType(Type::Array) From 0c4df0d3afefc3a4e4dd505ca55538a1ccfebacf Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 May 2024 19:05:52 +0200 Subject: [PATCH 214/266] added PhpNamespace::getClass() & getFunction() --- src/PhpGenerator/PhpNamespace.php | 54 ++++++++++++++++++---------- tests/PhpGenerator/PhpNamespace.phpt | 6 +++- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index b0e3c1a9..9ab63fcb 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -313,6 +313,30 @@ public function addEnum(string $name): EnumType } + /** + * Returns a class-like type from the namespace. + */ + public function getClass(string $name): ClassType|InterfaceType|TraitType|EnumType + { + return $this->classes[strtolower($name)] ?? throw new Nette\InvalidArgumentException("Class '$name' not found."); + } + + + /** + * Returns all class-like types in the namespace. + * @return (ClassType|InterfaceType|TraitType|EnumType)[] + */ + public function getClasses(): array + { + $res = []; + foreach ($this->classes as $class) { + $res[$class->getName()] = $class; + } + + return $res; + } + + /** * Removes a class-like type from namespace. */ @@ -340,24 +364,23 @@ public function addFunction(string $name): GlobalFunction /** - * Removes a function type from namespace. + * Returns a function from the namespace. */ - public function removeFunction(string $name): static + public function getFunction(string $name): GlobalFunction { - unset($this->functions[strtolower($name)]); - return $this; + return $this->functions[strtolower($name)] ?? throw new Nette\InvalidArgumentException("Function '$name' not found."); } /** - * Returns all class-like types in the namespace. - * @return (ClassType|InterfaceType|TraitType|EnumType)[] + * Returns all functions in the namespace. + * @return GlobalFunction[] */ - public function getClasses(): array + public function getFunctions(): array { $res = []; - foreach ($this->classes as $class) { - $res[$class->getName()] = $class; + foreach ($this->functions as $fn) { + $res[$fn->getName()] = $fn; } return $res; @@ -365,17 +388,12 @@ public function getClasses(): array /** - * Returns all functions in the namespace. - * @return GlobalFunction[] + * Removes a function type from namespace. */ - public function getFunctions(): array + public function removeFunction(string $name): static { - $res = []; - foreach ($this->functions as $fn) { - $res[$fn->getName()] = $fn; - } - - return $res; + unset($this->functions[strtolower($name)]); + return $this; } diff --git a/tests/PhpGenerator/PhpNamespace.phpt b/tests/PhpGenerator/PhpNamespace.phpt index e1fc4bb9..3b352de4 100644 --- a/tests/PhpGenerator/PhpNamespace.phpt +++ b/tests/PhpGenerator/PhpNamespace.phpt @@ -25,8 +25,10 @@ Assert::exception( $interfaceB = $namespace->addInterface('B'); Assert::same($namespace, $interfaceB->getNamespace()); +Assert::same($classA, $namespace->getClass('a')); + Assert::count(2, $namespace->getClasses()); -Assert::type(Nette\PhpGenerator\ClassType::class, $namespace->getClasses()['A']); +Assert::same($classA, $namespace->getClasses()['A']); $namespace->removeClass('a'); Assert::count(1, $namespace->getClasses()); @@ -39,6 +41,8 @@ Assert::exception( "Cannot add 'Foo', because it already exists.", ); +Assert::same($function, $namespace->getFunction('foo')); + Assert::count(1, $namespace->getFunctions()); Assert::same($function, $namespace->getFunctions()['foo']); $namespace->removeFunction('FOO'); From 78dcebd84addf336e6ed052b26659348a7ac2793 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 20 Mar 2024 00:02:47 +0100 Subject: [PATCH 215/266] GlobalFunction, Method: from() accepts first-class callables --- src/PhpGenerator/GlobalFunction.php | 6 ++-- src/PhpGenerator/Method.php | 2 +- .../PhpGenerator/GlobalFunction.from.81.phpt | 36 +++++++++++++++++++ tests/PhpGenerator/Method.from.81.phpt | 30 ++++++++++++++++ 4 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 tests/PhpGenerator/GlobalFunction.from.81.phpt create mode 100644 tests/PhpGenerator/Method.from.81.phpt diff --git a/src/PhpGenerator/GlobalFunction.php b/src/PhpGenerator/GlobalFunction.php index 3a6a5a3b..f25689a2 100644 --- a/src/PhpGenerator/GlobalFunction.php +++ b/src/PhpGenerator/GlobalFunction.php @@ -9,6 +9,8 @@ namespace Nette\PhpGenerator; +use Nette; + /** * Global function. @@ -20,9 +22,9 @@ final class GlobalFunction use Traits\CommentAware; use Traits\AttributeAware; - public static function from(string $function, bool $withBody = false): self + public static function from(string|\Closure $function, bool $withBody = false): self { - return (new Factory)->fromFunctionReflection(new \ReflectionFunction($function), $withBody); + return (new Factory)->fromFunctionReflection(Nette\Utils\Callback::toReflection($function), $withBody); } diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index 5c10fec4..b414f966 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -28,7 +28,7 @@ final class Method private bool $abstract = false; - public static function from(string|array $method): static + public static function from(string|array|\Closure $method): static { return (new Factory)->fromMethodReflection(Nette\Utils\Callback::toReflection($method)); } diff --git a/tests/PhpGenerator/GlobalFunction.from.81.phpt b/tests/PhpGenerator/GlobalFunction.from.81.phpt new file mode 100644 index 00000000..10cab3ff --- /dev/null +++ b/tests/PhpGenerator/GlobalFunction.from.81.phpt @@ -0,0 +1,36 @@ + Date: Sun, 12 May 2024 16:53:46 +0200 Subject: [PATCH 216/266] ClassType: cloning includes attributes and parameters --- src/PhpGenerator/ClassLike.php | 6 ++++++ src/PhpGenerator/ClassType.php | 3 ++- src/PhpGenerator/Closure.php | 6 ++++++ src/PhpGenerator/EnumType.php | 3 ++- src/PhpGenerator/GlobalFunction.php | 6 ++++++ src/PhpGenerator/InterfaceType.php | 3 ++- src/PhpGenerator/Method.php | 6 ++++++ src/PhpGenerator/TraitType.php | 3 ++- tests/PhpGenerator/ClassType.clone.phpt | 6 +++++- 9 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/PhpGenerator/ClassLike.php b/src/PhpGenerator/ClassLike.php index 15c2d30c..7be48d8a 100644 --- a/src/PhpGenerator/ClassLike.php +++ b/src/PhpGenerator/ClassLike.php @@ -140,4 +140,10 @@ protected function validateNames(array $names): void public function validate(): void { } + + + public function __clone(): void + { + $this->attributes = array_map(fn($attr) => clone $attr, $this->attributes); + } } diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index e6ababf6..f8481f15 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -260,8 +260,9 @@ public function validate(): void } - public function __clone() + public function __clone(): void { + parent::__clone(); $clone = fn($item) => clone $item; $this->consts = array_map($clone, $this->consts); $this->methods = array_map($clone, $this->methods); diff --git a/src/PhpGenerator/Closure.php b/src/PhpGenerator/Closure.php index e958a886..843feedb 100644 --- a/src/PhpGenerator/Closure.php +++ b/src/PhpGenerator/Closure.php @@ -57,4 +57,10 @@ public function addUse(string $name): Parameter { return $this->uses[] = new Parameter($name); } + + + public function __clone(): void + { + $this->parameters = array_map(fn($param) => clone $param, $this->parameters); + } } diff --git a/src/PhpGenerator/EnumType.php b/src/PhpGenerator/EnumType.php index 75b903bf..43456183 100644 --- a/src/PhpGenerator/EnumType.php +++ b/src/PhpGenerator/EnumType.php @@ -136,8 +136,9 @@ public function addMember(Method|Constant|EnumCase|TraitUse $member, bool $overw } - public function __clone() + public function __clone(): void { + parent::__clone(); $clone = fn($item) => clone $item; $this->consts = array_map($clone, $this->consts); $this->methods = array_map($clone, $this->methods); diff --git a/src/PhpGenerator/GlobalFunction.php b/src/PhpGenerator/GlobalFunction.php index f25689a2..c6b1e973 100644 --- a/src/PhpGenerator/GlobalFunction.php +++ b/src/PhpGenerator/GlobalFunction.php @@ -32,4 +32,10 @@ public function __toString(): string { return (new Printer)->printFunction($this); } + + + public function __clone(): void + { + $this->parameters = array_map(fn($param) => clone $param, $this->parameters); + } } diff --git a/src/PhpGenerator/InterfaceType.php b/src/PhpGenerator/InterfaceType.php index ac082e0a..8743f8d0 100644 --- a/src/PhpGenerator/InterfaceType.php +++ b/src/PhpGenerator/InterfaceType.php @@ -69,8 +69,9 @@ public function addMember(Method|Constant $member, bool $overwrite = false): sta } - public function __clone() + public function __clone(): void { + parent::__clone(); $clone = fn($item) => clone $item; $this->consts = array_map($clone, $this->consts); $this->methods = array_map($clone, $this->methods); diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index b414f966..0f6eeae8 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -100,4 +100,10 @@ public function validate(): void throw new Nette\InvalidStateException("Method $this->name() cannot be abstract and final or private at the same time."); } } + + + public function __clone(): void + { + $this->parameters = array_map(fn($param) => clone $param, $this->parameters); + } } diff --git a/src/PhpGenerator/TraitType.php b/src/PhpGenerator/TraitType.php index 9c7b78b5..8c80b5d8 100644 --- a/src/PhpGenerator/TraitType.php +++ b/src/PhpGenerator/TraitType.php @@ -42,8 +42,9 @@ public function addMember(Method|Property|Constant|TraitUse $member, bool $overw } - public function __clone() + public function __clone(): void { + parent::__clone(); $clone = fn($item) => clone $item; $this->consts = array_map($clone, $this->consts); $this->methods = array_map($clone, $this->methods); diff --git a/tests/PhpGenerator/ClassType.clone.phpt b/tests/PhpGenerator/ClassType.clone.phpt index 0d632bfe..0d007eb2 100644 --- a/tests/PhpGenerator/ClassType.clone.phpt +++ b/tests/PhpGenerator/ClassType.clone.phpt @@ -10,12 +10,16 @@ require __DIR__ . '/../bootstrap.php'; $class = new ClassType('Example'); +$class->addAttribute('Attr'); $class->addConstant('A', 10); $class->addProperty('a'); -$class->addMethod('a'); +$class->addMethod('a') + ->addParameter('foo'); $dolly = clone $class; +Assert::notSame($dolly->getAttributes(), $class->getAttributes()); Assert::notSame($dolly->getConstants(), $class->getConstants()); Assert::notSame($dolly->getProperty('a'), $class->getProperty('a')); Assert::notSame($dolly->getMethod('a'), $class->getMethod('a')); +Assert::notSame($dolly->getMethod('a')->getParameter('foo'), $class->getMethod('a')->getParameter('foo')); From b4da5264c4a45a11cc20dbb56c8e8c6a95c7e270 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 May 2024 15:41:01 +0200 Subject: [PATCH 217/266] Revert "Factory: properties in readonly classes are not readonly" This reverts commit ea40f2f8d743d5ca5a203f2ece4f80940aba115d. --- src/PhpGenerator/Extractor.php | 2 +- src/PhpGenerator/Factory.php | 2 +- src/PhpGenerator/Printer.php | 3 ++- tests/PhpGenerator/ClassType.readonly.phpt | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 6220cddd..e783ef71 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -329,7 +329,7 @@ private function addPropertyToClass(ClassLike $class, Node\Stmt\Property $node): $prop->setValue($this->toValue($item->default)); } - $prop->setReadOnly(method_exists($node, 'isReadonly') && $node->isReadonly()); + $prop->setReadOnly((method_exists($node, 'isReadonly') && $node->isReadonly()) || ($class instanceof ClassType && $class->isReadOnly())); $this->addCommentAndAttributes($prop, $node); } } diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 544e3f35..431d28e8 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -257,7 +257,7 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property $prop->setType((string) $from->getType()); $prop->setInitialized($from->hasType() && array_key_exists($prop->getName(), $defaults)); - $prop->setReadOnly(PHP_VERSION_ID >= 80100 && $from->isReadOnly() && !(PHP_VERSION_ID >= 80200 && $from->getDeclaringClass()->isReadOnly())); + $prop->setReadOnly(PHP_VERSION_ID >= 80100 && $from->isReadOnly()); $prop->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); $prop->setAttributes($this->getAttributes($from)); return $prop; diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 1d9528ae..ea2d5468 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -170,6 +170,7 @@ public function printClass( } } + $readOnlyClass = $class instanceof ClassType && $class->isReadOnly(); $consts = []; $methods = []; if ( @@ -203,7 +204,7 @@ public function printClass( $type = $property->getType(); $def = (($property->getVisibility() ?: 'public') . ($property->isStatic() ? ' static' : '') - . ($property->isReadOnly() && $type ? ' readonly' : '') + . (!$readOnlyClass && $property->isReadOnly() && $type ? ' readonly' : '') . ' ' . ltrim($this->printType($type, $property->isNullable()) . ' ') . '$' . $property->getName()); diff --git a/tests/PhpGenerator/ClassType.readonly.phpt b/tests/PhpGenerator/ClassType.readonly.phpt index 07817b4c..1f34721b 100644 --- a/tests/PhpGenerator/ClassType.readonly.phpt +++ b/tests/PhpGenerator/ClassType.readonly.phpt @@ -14,10 +14,10 @@ require __DIR__ . '/../bootstrap.php'; require __DIR__ . '/fixtures/classes.82.php'; $class = ClassType::from(new Abc\Class13); -Assert::false($class->getProperty('foo')->isReadOnly()); +Assert::true($class->getProperty('foo')->isReadOnly()); Assert::false($class->getMethod('__construct')->getParameter('bar')->isReadOnly()); $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.82.php')))->extractAll(); $class = $file->getClasses()[Abc\Class13::class]; -Assert::false($class->getProperty('foo')->isReadOnly()); +Assert::true($class->getProperty('foo')->isReadOnly()); Assert::false($class->getMethod('__construct')->getParameter('bar')->isReadOnly()); From ea9deedc29231802ede282c3cb8c8bd43956781e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 May 2024 15:41:01 +0200 Subject: [PATCH 218/266] Factory, Extractor: sets flag readonly for promoted properties [Closes #158] --- src/PhpGenerator/Extractor.php | 3 +++ src/PhpGenerator/Factory.php | 2 +- src/PhpGenerator/Method.php | 2 ++ src/PhpGenerator/Printer.php | 4 ++++ tests/PhpGenerator/ClassType.from.81.phpt | 2 ++ tests/PhpGenerator/ClassType.readonly.phpt | 4 ++-- .../expected/ClassType.from.81.expect | 16 ++++++++++++++++ 7 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index e783ef71..5d39905e 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -343,6 +343,9 @@ private function addMethodToClass(ClassLike $class, Node\Stmt\ClassMethod $node) $method->setStatic($node->isStatic()); $method->setVisibility($this->toVisibility($node->flags)); $this->setupFunction($method, $node); + if ($method->getName() === Method::Constructor && $class instanceof ClassType && $class->isReadOnly()) { + array_map(fn($param) => $param instanceof PromotedParameter ? $param->setReadOnly() : $param, $method->getParameters()); + } } diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 431d28e8..abc1f763 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -200,7 +200,7 @@ public function fromCallable(callable $from): Method|GlobalFunction|Closure public function fromParameterReflection(\ReflectionParameter $from): Parameter { $param = $from->isPromoted() - ? new PromotedParameter($from->name) + ? (new PromotedParameter($from->name))->setReadOnly(PHP_VERSION_ID >= 80100 && $from->getDeclaringClass()->getProperty($from->name)->isReadonly()) : new Parameter($from->name); $param->setReference($from->isPassedByReference()); $param->setType((string) $from->getType()); diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index 0f6eeae8..0e8e765b 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -23,6 +23,8 @@ final class Method use Traits\CommentAware; use Traits\AttributeAware; + public const Constructor = '__construct'; + private bool $static = false; private bool $final = false; private bool $abstract = false; diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index ea2d5468..4a1cf447 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -193,6 +193,10 @@ public function printClass( } foreach ($class->getMethods() as $method) { + if ($readOnlyClass && $method->getName() === Method::Constructor) { + $method = clone $method; + array_map(fn($param) => $param instanceof PromotedParameter ? $param->setReadOnly(false) : null, $method->getParameters()); + } $methods[] = $this->printMethod($method, $namespace, $class->isInterface()); } } diff --git a/tests/PhpGenerator/ClassType.from.81.phpt b/tests/PhpGenerator/ClassType.from.81.phpt index 6dd3db33..ce30dfc1 100644 --- a/tests/PhpGenerator/ClassType.from.81.phpt +++ b/tests/PhpGenerator/ClassType.from.81.phpt @@ -12,5 +12,7 @@ require __DIR__ . '/../bootstrap.php'; require __DIR__ . '/fixtures/classes.81.php'; $res[] = ClassType::from(new Abc\Class11); +$res[] = ClassType::from(Abc\Attr::class); +$res[] = ClassType::from(Abc\Class12::class); sameFile(__DIR__ . '/expected/ClassType.from.81.expect', implode("\n", $res)); diff --git a/tests/PhpGenerator/ClassType.readonly.phpt b/tests/PhpGenerator/ClassType.readonly.phpt index 1f34721b..33079aa7 100644 --- a/tests/PhpGenerator/ClassType.readonly.phpt +++ b/tests/PhpGenerator/ClassType.readonly.phpt @@ -15,9 +15,9 @@ require __DIR__ . '/fixtures/classes.82.php'; $class = ClassType::from(new Abc\Class13); Assert::true($class->getProperty('foo')->isReadOnly()); -Assert::false($class->getMethod('__construct')->getParameter('bar')->isReadOnly()); +Assert::true($class->getMethod('__construct')->getParameter('bar')->isReadOnly()); $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.82.php')))->extractAll(); $class = $file->getClasses()[Abc\Class13::class]; Assert::true($class->getProperty('foo')->isReadOnly()); -Assert::false($class->getMethod('__construct')->getParameter('bar')->isReadOnly()); +Assert::true($class->getMethod('__construct')->getParameter('bar')->isReadOnly()); diff --git a/tests/PhpGenerator/expected/ClassType.from.81.expect b/tests/PhpGenerator/expected/ClassType.from.81.expect index f52acd06..22b0d789 100644 --- a/tests/PhpGenerator/expected/ClassType.from.81.expect +++ b/tests/PhpGenerator/expected/ClassType.from.81.expect @@ -16,3 +16,19 @@ class Class11 { } } + +#[\Attribute] +class Attr +{ +} + +class Class12 +{ + private readonly string $bar; + + + public function __construct( + public readonly string $foo, + ) { + } +} From 690b00d81d42d5633e4457c43ef9754573b6f9d6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 May 2024 19:31:02 +0200 Subject: [PATCH 219/266] Printer: refactoring --- src/PhpGenerator/Printer.php | 64 +++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 4a1cf447..42e5cf73 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -180,16 +180,7 @@ public function printClass( || $class instanceof EnumType ) { foreach ($class->getConstants() as $const) { - $def = ($const->isFinal() ? 'final ' : '') - . ($const->getVisibility() ? $const->getVisibility() . ' ' : '') - . 'const ' - . ltrim($this->printType($const->getType(), nullable: false) . ' ') - . $const->getName() . ' = '; - - $consts[] = $this->printDocComment($const) - . $this->printAttributes($const->getAttributes()) - . $def - . $this->dump($const->getValue(), strlen($def)) . ";\n"; + $consts[] = $this->printConstant($const); } foreach ($class->getMethods() as $method) { @@ -204,22 +195,7 @@ public function printClass( $properties = []; if ($class instanceof ClassType || $class instanceof TraitType) { foreach ($class->getProperties() as $property) { - $property->validate(); - $type = $property->getType(); - $def = (($property->getVisibility() ?: 'public') - . ($property->isStatic() ? ' static' : '') - . (!$readOnlyClass && $property->isReadOnly() && $type ? ' readonly' : '') - . ' ' - . ltrim($this->printType($type, $property->isNullable()) . ' ') - . '$' . $property->getName()); - - $properties[] = $this->printDocComment($property) - . $this->printAttributes($property->getAttributes()) - . $def - . ($property->getValue() === null && !$property->isInitialized() - ? '' - : ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = ' - . ";\n"; + $properties[] = $this->printProperty($property, $readOnlyClass); } } @@ -380,6 +356,42 @@ private function formatParameters(Closure|GlobalFunction|Method $function, bool } + private function printConstant(Constant $const): string + { + $def = ($const->isFinal() ? 'final ' : '') + . ($const->getVisibility() ? $const->getVisibility() . ' ' : '') + . 'const ' + . ltrim($this->printType($const->getType(), nullable: false) . ' ') + . $const->getName() . ' = '; + + return $this->printDocComment($const) + . $this->printAttributes($const->getAttributes()) + . $def + . $this->dump($const->getValue(), strlen($def)) . ";\n"; + } + + + private function printProperty(Property $property, bool $readOnlyClass = false): string + { + $property->validate(); + $type = $property->getType(); + $def = (($property->getVisibility() ?: 'public') + . ($property->isStatic() ? ' static' : '') + . (!$readOnlyClass && $property->isReadOnly() && $type ? ' readonly' : '') + . ' ' + . ltrim($this->printType($type, $property->isNullable()) . ' ') + . '$' . $property->getName()); + + return $this->printDocComment($property) + . $this->printAttributes($property->getAttributes()) + . $def + . ($property->getValue() === null && !$property->isInitialized() + ? '' + : ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = ' + . ";\n"; + } + + protected function printType(?string $type, bool $nullable): string { if ($type === null) { From 855671d11625cfa9b7d209f061e2e86212941de4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 19 Aug 2024 02:45:43 +0200 Subject: [PATCH 220/266] cs --- src/PhpGenerator/Printer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 42e5cf73..187dacd4 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -352,7 +352,6 @@ private function formatParameters(Closure|GlobalFunction|Method $function, bool return $multiline ? "(\n" . $this->indent($res) . ')' : '(' . substr($res, 0, -2) . ')'; - } From 05cd5e2e51a23b5658477ad81860fce2a91c24a3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 16 May 2024 23:16:06 +0200 Subject: [PATCH 221/266] readme: added jumbo --- readme.md | 46 +++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/readme.md b/readme.md index 2f7da038..05e4b6a9 100644 --- a/readme.md +++ b/readme.md @@ -1,13 +1,21 @@ -Nette PHP Generator [![Latest Stable Version](https://poser.pugx.org/nette/php-generator/v/stable)](https://github.com/nette/php-generator/releases) [![Downloads this Month](https://img.shields.io/packagist/dm/nette/php-generator.svg)](https://packagist.org/packages/nette/php-generator) -=================== +[![Nette PHP Generator](https://github.com/nette/php-generator/assets/194960/8a2c83bd-daea-475f-994c-9c951de88501)](https://doc.nette.org/en/php-generator) + +[![Latest Stable Version](https://poser.pugx.org/nette/php-generator/v/stable)](https://github.com/nette/php-generator/releases) [![Downloads this Month](https://img.shields.io/packagist/dm/nette/php-generator.svg)](https://packagist.org/packages/nette/php-generator) + +  Are you looking for a tool to generate PHP code for [classes](#classes), [functions](#global-functions), or complete [PHP files](#php-files)? +

+ ✅ Supports all the latest PHP features like [enums](#enums), [attributes](#attributes), etc.
✅ Allows you to easily modify [existing classes](#generating-from-existing-ones)
✅ Output compliant with [PSR-12 / PER coding style](#printer-and-psr-compliance)
✅ Highly mature, stable, and widely used library +

+ +  Installation ------------ @@ -18,8 +26,9 @@ Download and install the library using the [Composer](https://doc.nette.org/en/b composer require nette/php-generator ``` -For PHP compatibility, see the [table](#compatibility-table). Documentation even for older versions can be found on the [library's website](https://doc.nette.org/php-generator). +PhpGenerator 4.1 is compatible with PHP 8.0 to 8.3. Documentation can be found on the [library's website](https://doc.nette.org/php-generator). +  [Support Me](https://github.com/sponsors/dg) -------------------------------------------- @@ -30,6 +39,7 @@ Do you like PHP Generator? Are you looking forward to the new features? Thank you! +  Classes ------- @@ -175,6 +185,7 @@ $methodRecount = $methodCount->cloneWithName('recount'); $class->addMember($methodRecount); ``` +  Interfaces or Traits -------------------- @@ -210,6 +221,7 @@ class Demo } ``` +  Enums ----- @@ -247,6 +259,7 @@ $enum->addCase('Diamonds', '♦'); For each *case*, you can add a comment or [attributes](#attributes) using `addComment()` or `addAttribute()`. +  Anonymous Classes ----------------- @@ -272,6 +285,7 @@ $obj = new class ($val) { }; ``` +  Global Functions ---------------- @@ -298,6 +312,7 @@ function foo($a, $b) } ``` +  Anonymous Functions ------------------- @@ -325,6 +340,7 @@ function ($a, $b) use (&$c) { } ``` +  Short Arrow Functions --------------------- @@ -346,6 +362,7 @@ The result is: fn($a, $b) => $a + $b ``` +  Method and Function Signatures ------------------------------ @@ -386,6 +403,7 @@ function count(...$items) } ``` +  Method and Function Bodies -------------------------- @@ -476,6 +494,7 @@ function foo($a) } ``` +  Printer and PSR Compliance -------------------------- @@ -531,6 +550,7 @@ The standard `Printer` formats the code as we do throughout Nette. Since Nette w The major difference is the use of tabs instead of spaces. We know that by using tabs in our projects, we allow for width customization, which is [essential for people with visual impairments](https://doc.nette.org/en/contributing/coding-standard#toc-tabs-instead-of-spaces). An example of a minor difference is placing the curly brace on a separate line for functions and methods, always. The PSR recommendation seems illogical to us and [leads to reduced code clarity](https://doc.nette.org/en/contributing/coding-standard#toc-wrapping-and-braces). +  Types ----- @@ -549,6 +569,7 @@ $member->setType(null); // removes the type The same applies to the `setReturnType()` method. +  Literals -------- @@ -595,6 +616,7 @@ Literal::new(Demo::class, [$a, 'foo' => $b]); // generates for example: new Demo(10, foo: 20) ``` +  Attributes ---------- @@ -641,6 +663,7 @@ class Demo } ``` +  Namespace --------- @@ -687,6 +710,7 @@ echo $namespace->resolveName('Bar'); // 'Foo\Bar' echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range' ``` +  Class Names Resolving --------------------- @@ -741,6 +765,7 @@ $printer->setTypeResolving(false); echo $printer->printNamespace($namespace); ``` +  PHP Files --------- @@ -790,6 +815,7 @@ function foo() **Please note:** No additional code can be added to the files outside of functions and classes. +  Generating from Existing Ones ----------------------------- @@ -818,6 +844,7 @@ $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); ``` +  Loading from PHP Files ---------------------- @@ -849,6 +876,7 @@ It requires `nikic/php-parser` to be installed. *(If you need to manipulate global code in files or individual statements in method bodies, it's better to use the `nikic/php-parser` library directly.)* +  Variable Dumping ---------------- @@ -862,15 +890,3 @@ $var = ['a', 'b', 123]; echo $dumper->dump($var); // outputs ['a', 'b', 123] ``` - - -Compatibility Table -------------------- - -- PhpGenerator 4.1 is compatible with PHP 8.0 to 8.3 -- PhpGenerator 4.0 is compatible with PHP 8.0 to 8.3 -- PhpGenerator 3.6 is compatible with PHP 7.2 to 8.2 -- PhpGenerator 3.2 – 3.5 is compatible with PHP 7.1 to 8.0 -- PhpGenerator 3.1 is compatible with PHP 7.1 to 7.3 -- PhpGenerator 3.0 is compatible with PHP 7.0 to 7.3 -- PhpGenerator 2.6 is compatible with PHP 5.6 to 7.3 From 9afe1f025a82aed545cdc47102de985c8be8c0a1 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 18 Jun 2024 22:55:13 +0200 Subject: [PATCH 222/266] Printer::printType() refactoring --- src/PhpGenerator/Printer.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 187dacd4..98d44982 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -401,13 +401,9 @@ protected function printType(?string $type, bool $nullable): string $type = $this->namespace->simplifyType($type); } - if ($nullable && strcasecmp($type, 'mixed')) { - $type = str_contains($type, '|') - ? $type . '|null' - : '?' . $type; - } - - return $type; + return $nullable + ? Type::nullable($type) + : $type; } From 2debfd610eecba406c6701ebdfb22f40701c3385 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 18 Jun 2024 22:20:40 +0200 Subject: [PATCH 223/266] Parameter & Property: isNullable() returns true when default value is null (possible BC break) For compatibility with PHP 8.4 --- src/PhpGenerator/Parameter.php | 2 +- src/PhpGenerator/Property.php | 2 +- tests/PhpGenerator/expected/ClassType.expect | 2 +- tests/PhpGenerator/expected/Extractor.bodies.expect | 2 +- tests/PhpGenerator/expected/Extractor.bodies.resolving.expect | 2 +- .../PhpGenerator/expected/Extractor.bodies.unresolving.expect | 2 +- tests/PhpGenerator/expected/Extractor.classes.expect | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/PhpGenerator/Parameter.php b/src/PhpGenerator/Parameter.php index b40d5e00..20651a31 100644 --- a/src/PhpGenerator/Parameter.php +++ b/src/PhpGenerator/Parameter.php @@ -66,7 +66,7 @@ public function setNullable(bool $state = true): static public function isNullable(): bool { - return $this->nullable; + return $this->nullable || ($this->hasDefaultValue && $this->defaultValue === null); } diff --git a/src/PhpGenerator/Property.php b/src/PhpGenerator/Property.php index 17a42482..49db18dd 100644 --- a/src/PhpGenerator/Property.php +++ b/src/PhpGenerator/Property.php @@ -83,7 +83,7 @@ public function setNullable(bool $state = true): static public function isNullable(): bool { - return $this->nullable; + return $this->nullable || ($this->initialized && $this->value === null); } diff --git a/tests/PhpGenerator/expected/ClassType.expect b/tests/PhpGenerator/expected/ClassType.expect index 0c64edb0..80cef326 100644 --- a/tests/PhpGenerator/expected/ClassType.expect +++ b/tests/PhpGenerator/expected/ClassType.expect @@ -27,7 +27,7 @@ abstract class Example extends ParentClass implements IExample, IOne public $order = RecursiveIteratorIterator::SELF_FIRST; public readonly array $typed1; public ?array $typed2 = null; - public array $typed3 = null; + public ?array $typed3 = null; public static $sections = ['first' => true]; diff --git a/tests/PhpGenerator/expected/Extractor.bodies.expect b/tests/PhpGenerator/expected/Extractor.bodies.expect index b72737be..a4bdb604 100644 --- a/tests/PhpGenerator/expected/Extractor.bodies.expect +++ b/tests/PhpGenerator/expected/Extractor.bodies.expect @@ -48,7 +48,7 @@ abstract class Class7 } - function resolving($a = a\FOO, self $b = null, $c = self::FOO) + function resolving($a = a\FOO, ?self $b = null, $c = self::FOO) { // constants echo FOO; diff --git a/tests/PhpGenerator/expected/Extractor.bodies.resolving.expect b/tests/PhpGenerator/expected/Extractor.bodies.resolving.expect index e47b20fe..470375c9 100644 --- a/tests/PhpGenerator/expected/Extractor.bodies.resolving.expect +++ b/tests/PhpGenerator/expected/Extractor.bodies.resolving.expect @@ -43,7 +43,7 @@ abstract class Class7 } - function resolving($a = \Abc\a\FOO, self $b = null, $c = self::FOO) + function resolving($a = \Abc\a\FOO, ?self $b = null, $c = self::FOO) { // constants echo FOO; diff --git a/tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect b/tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect index 63747419..b181d53a 100644 --- a/tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect +++ b/tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect @@ -43,7 +43,7 @@ abstract class Class7 } - function resolving($a = \Abc\a\FOO, self $b = null, $c = self::FOO) + function resolving($a = \Abc\a\FOO, ?self $b = null, $c = self::FOO) { // constants echo FOO; diff --git a/tests/PhpGenerator/expected/Extractor.classes.expect b/tests/PhpGenerator/expected/Extractor.classes.expect index c37fe1f4..d2c7c136 100644 --- a/tests/PhpGenerator/expected/Extractor.classes.expect +++ b/tests/PhpGenerator/expected/Extractor.classes.expect @@ -72,7 +72,7 @@ class Class2 extends Class1 implements Interface2 } - private function func4(array $a = [], Class2 $b = null, $c = Unknown::ABC) + private function func4(array $a = [], ?Class2 $b = null, $c = Unknown::ABC) { } @@ -94,7 +94,7 @@ class Class4 class Class5 { - public function func1(\A $a, ?\B $b, ?\C $c = null, \D $d = null, ?int $i = 1, ?array $arr = []) + public function func1(\A $a, ?\B $b, ?\C $c = null, ?\D $d = null, ?int $i = 1, ?array $arr = []) { } From 6c438e0155fcd98d6099bbefa2dc55caa11104cb Mon Sep 17 00:00:00 2001 From: Andrew Nagy <564256+tm1000@users.noreply.github.com> Date: Tue, 10 Sep 2024 02:02:09 -0700 Subject: [PATCH 224/266] Added TraitsAware::hasTrait method (#163) --- src/PhpGenerator/Traits/TraitsAware.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/PhpGenerator/Traits/TraitsAware.php b/src/PhpGenerator/Traits/TraitsAware.php index adf2ad4f..7fb051be 100644 --- a/src/PhpGenerator/Traits/TraitsAware.php +++ b/src/PhpGenerator/Traits/TraitsAware.php @@ -67,4 +67,10 @@ public function removeTrait(string $name): static unset($this->traits[$name]); return $this; } + + + public function hasTrait(string $name): bool + { + return isset($this->traits[$name]); + } } From ca63f9858a88c1f8a41904dafe2523c7429f2159 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 19 Aug 2024 02:45:41 +0200 Subject: [PATCH 225/266] added ClassManipulator --- readme.md | 33 +++++++ src/PhpGenerator/ClassManipulator.php | 95 +++++++++++++++++++ src/PhpGenerator/ClassType.php | 43 +-------- .../ClassManipulator.implementInterface.phpt | 29 ++++++ ...pt => ClassManipulator.inheritMethod.phpt} | 18 ++-- ... => ClassManipulator.inheritProperty.phpt} | 18 ++-- 6 files changed, 183 insertions(+), 53 deletions(-) create mode 100644 src/PhpGenerator/ClassManipulator.php create mode 100644 tests/PhpGenerator/ClassManipulator.implementInterface.phpt rename tests/PhpGenerator/{Method.inherit.phpt => ClassManipulator.inheritMethod.phpt} (59%) rename tests/PhpGenerator/{Property.inherit.phpt => ClassManipulator.inheritProperty.phpt} (57%) diff --git a/readme.md b/readme.md index 05e4b6a9..13d74d53 100644 --- a/readme.md +++ b/readme.md @@ -878,6 +878,39 @@ It requires `nikic/php-parser` to be installed.   +Class Manipulator +----------------- + +The [ClassManipulator](https://api.nette.org/php-generator/master/Nette/PhpGenerator/ClassManipulator.html) class provides tools for manipulating classes. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` + +The `inheritMethod()` method copies a method from a parent class or implemented interface into your class. This allows you to override the method or extend its signature: + +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` + +The `inheritProperty()` method copies a property from a parent class into your class. This is useful when you want to have the same property in your class, but possibly with a different default value: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('new value'); +``` + +The `implementInterface()` method automatically implements all methods from the given interface in your class: + +```php +$manipulator->implementInterface(SomeInterface::class); +// Now your class implements SomeInterface and includes all its methods +``` + +  + Variable Dumping ---------------- diff --git a/src/PhpGenerator/ClassManipulator.php b/src/PhpGenerator/ClassManipulator.php new file mode 100644 index 00000000..d97ee319 --- /dev/null +++ b/src/PhpGenerator/ClassManipulator.php @@ -0,0 +1,95 @@ +class->getExtends(); + if ($this->class->hasProperty($name)) { + return $returnIfExists + ? $this->class->getProperty($name) + : throw new Nette\InvalidStateException("Cannot inherit property '$name', because it already exists."); + + } elseif (!$extends) { + throw new Nette\InvalidStateException("Class '{$this->class->getName()}' has not setExtends() set."); + } + + try { + $rp = new \ReflectionProperty($extends, $name); + } catch (\ReflectionException) { + throw new Nette\InvalidStateException("Property '$name' has not been found in ancestor {$extends}"); + } + + $property = (new Factory)->fromPropertyReflection($rp); + $this->class->addMember($property); + return $property; + } + + + /** + * Inherits method from parent class or interface. + */ + public function inheritMethod(string $name, bool $returnIfExists = false): Method + { + $parents = [...(array) $this->class->getExtends(), ...$this->class->getImplements()]; + if ($this->class->hasMethod($name)) { + return $returnIfExists + ? $this->class->getMethod($name) + : throw new Nette\InvalidStateException("Cannot inherit method '$name', because it already exists."); + + } elseif (!$parents) { + throw new Nette\InvalidStateException("Class '{$this->class->getName()}' has neither setExtends() nor setImplements() set."); + } + + foreach ($parents as $parent) { + try { + $rm = new \ReflectionMethod($parent, $name); + } catch (\ReflectionException) { + continue; + } + $method = (new Factory)->fromMethodReflection($rm); + $this->class->addMember($method); + return $method; + } + + throw new Nette\InvalidStateException("Method '$name' has not been found in any ancestor: " . implode(', ', $parents)); + } + + + /** + * Implements all methods from the given interface. + */ + public function implementInterface(string $interfaceName): void + { + $interface = new \ReflectionClass($interfaceName); + if (!$interface->isInterface()) { + throw new Nette\InvalidArgumentException("Class '$interfaceName' is not an interface."); + } + + $this->class->addImplement($interfaceName); + foreach ($interface->getMethods() as $method) { + $this->inheritMethod($method->getName(), returnIfExists: true); + } + } +} diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index f8481f15..4e082685 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -195,55 +195,20 @@ public function addMember(Method|Property|Constant|TraitUse $member, bool $overw /** - * Inherits property from parent class. + * @deprecated use ClassManipulator::inheritProperty() */ public function inheritProperty(string $name, bool $returnIfExists = false): Property { - if (isset($this->properties[$name])) { - return $returnIfExists - ? $this->properties[$name] - : throw new Nette\InvalidStateException("Cannot inherit property '$name', because it already exists."); - - } elseif (!$this->extends) { - throw new Nette\InvalidStateException("Class '{$this->getName()}' has not setExtends() set."); - } - - try { - $rp = new \ReflectionProperty($this->extends, $name); - } catch (\ReflectionException) { - throw new Nette\InvalidStateException("Property '$name' has not been found in ancestor {$this->extends}"); - } - - return $this->properties[$name] = (new Factory)->fromPropertyReflection($rp); + return (new ClassManipulator($this))->inheritProperty($name, $returnIfExists); } /** - * Inherits method from parent class or interface. + * @deprecated use ClassManipulator::inheritMethod() */ public function inheritMethod(string $name, bool $returnIfExists = false): Method { - $lower = strtolower($name); - $parents = [...(array) $this->extends, ...$this->implements]; - if (isset($this->methods[$lower])) { - return $returnIfExists - ? $this->methods[$lower] - : throw new Nette\InvalidStateException("Cannot inherit method '$name', because it already exists."); - - } elseif (!$parents) { - throw new Nette\InvalidStateException("Class '{$this->getName()}' has neither setExtends() nor setImplements() set."); - } - - foreach ($parents as $parent) { - try { - $rm = new \ReflectionMethod($parent, $name); - } catch (\ReflectionException) { - continue; - } - return $this->methods[$lower] = (new Factory)->fromMethodReflection($rm); - } - - throw new Nette\InvalidStateException("Method '$name' has not been found in any ancestor: " . implode(', ', $parents)); + return (new ClassManipulator($this))->inheritMethod($name, $returnIfExists); } diff --git a/tests/PhpGenerator/ClassManipulator.implementInterface.phpt b/tests/PhpGenerator/ClassManipulator.implementInterface.phpt new file mode 100644 index 00000000..a0e18e52 --- /dev/null +++ b/tests/PhpGenerator/ClassManipulator.implementInterface.phpt @@ -0,0 +1,29 @@ +implementInterface(TestInterface::class); +Assert::true(in_array(TestInterface::class, $class->getImplements(), true)); +Assert::true($class->hasMethod('testMethod')); + +// Test exception for non-interface +Assert::exception( + fn() => $manipulator->implementInterface(stdClass::class), + InvalidArgumentException::class, +); diff --git a/tests/PhpGenerator/Method.inherit.phpt b/tests/PhpGenerator/ClassManipulator.inheritMethod.phpt similarity index 59% rename from tests/PhpGenerator/Method.inherit.phpt rename to tests/PhpGenerator/ClassManipulator.inheritMethod.phpt index 6fcc0523..4927e69c 100644 --- a/tests/PhpGenerator/Method.inherit.phpt +++ b/tests/PhpGenerator/ClassManipulator.inheritMethod.phpt @@ -2,6 +2,8 @@ declare(strict_types=1); +use Nette\PhpGenerator\ClassManipulator; +use Nette\PhpGenerator\ClassType; use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -16,9 +18,10 @@ class Foo // missing parent -$class = new Nette\PhpGenerator\ClassType('Test'); +$class = new ClassType('Test'); +$manipulator = new ClassManipulator($class); Assert::exception( - fn() => $class->inheritMethod('bar'), + fn() => $manipulator->inheritMethod('bar'), Nette\InvalidStateException::class, "Class 'Test' has neither setExtends() nor setImplements() set.", ); @@ -26,16 +29,17 @@ Assert::exception( $class->setExtends('Unknown1'); $class->addImplement('Unknown2'); Assert::exception( - fn() => $class->inheritMethod('bar'), + fn() => $manipulator->inheritMethod('bar'), Nette\InvalidStateException::class, "Method 'bar' has not been found in any ancestor: Unknown1, Unknown2", ); // implement method -$class = new Nette\PhpGenerator\ClassType('Test'); +$class = new ClassType('Test'); $class->setExtends(Foo::class); -$method = $class->inheritMethod('bar'); +$manipulator = new ClassManipulator($class); +$method = $manipulator->inheritMethod('bar'); Assert::match(<<<'XX' public function bar(int $a, ...$b): void { @@ -43,9 +47,9 @@ Assert::match(<<<'XX' XX, (string) $method); -Assert::same($method, $class->inheritMethod('bar', returnIfExists: true)); +Assert::same($method, $manipulator->inheritMethod('bar', returnIfExists: true)); Assert::exception( - fn() => $class->inheritMethod('bar', returnIfExists: false), + fn() => $manipulator->inheritMethod('bar', returnIfExists: false), Nette\InvalidStateException::class, "Cannot inherit method 'bar', because it already exists.", ); diff --git a/tests/PhpGenerator/Property.inherit.phpt b/tests/PhpGenerator/ClassManipulator.inheritProperty.phpt similarity index 57% rename from tests/PhpGenerator/Property.inherit.phpt rename to tests/PhpGenerator/ClassManipulator.inheritProperty.phpt index e110dae8..d9b9bf1b 100644 --- a/tests/PhpGenerator/Property.inherit.phpt +++ b/tests/PhpGenerator/ClassManipulator.inheritProperty.phpt @@ -2,6 +2,8 @@ declare(strict_types=1); +use Nette\PhpGenerator\ClassManipulator; +use Nette\PhpGenerator\ClassType; use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -14,25 +16,27 @@ class Foo // missing parent -$class = new Nette\PhpGenerator\ClassType('Test'); +$class = new ClassType('Test'); +$manipulator = new ClassManipulator($class); Assert::exception( - fn() => $class->inheritProperty('bar'), + fn() => $manipulator->inheritProperty('bar'), Nette\InvalidStateException::class, "Class 'Test' has not setExtends() set.", ); $class->setExtends('Unknown'); Assert::exception( - fn() => $class->inheritProperty('bar'), + fn() => $manipulator->inheritProperty('bar'), Nette\InvalidStateException::class, "Property 'bar' has not been found in ancestor Unknown", ); // implement property -$class = new Nette\PhpGenerator\ClassType('Test'); +$class = new ClassType('Test'); $class->setExtends(Foo::class); -$prop = $class->inheritProperty('bar'); +$manipulator = new ClassManipulator($class); +$prop = $manipulator->inheritProperty('bar'); Assert::match(<<<'XX' class Test extends Foo { @@ -41,9 +45,9 @@ Assert::match(<<<'XX' XX, (string) $class); -Assert::same($prop, $class->inheritProperty('bar', returnIfExists: true)); +Assert::same($prop, $manipulator->inheritProperty('bar', returnIfExists: true)); Assert::exception( - fn() => $class->inheritProperty('bar', returnIfExists: false), + fn() => $manipulator->inheritProperty('bar', returnIfExists: false), Nette\InvalidStateException::class, "Cannot inherit property 'bar', because it already exists.", ); From c90961e782ae86e517fe5ed732eb2b512945565b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 18 Jun 2024 22:20:40 +0200 Subject: [PATCH 226/266] support for PHP 8.4 --- .github/workflows/tests.yml | 2 +- composer.json | 2 +- readme.md | 2 +- tests/PhpGenerator/fixtures/bodies.php | 2 +- tests/PhpGenerator/fixtures/classes.php | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1189b342..ebeec0ac 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['8.0', '8.1', '8.2', '8.3'] + php: ['8.0', '8.1', '8.2', '8.3', '8.4'] fail-fast: false diff --git a/composer.json b/composer.json index b04fb925..c7026efc 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": "8.0 - 8.3", + "php": "8.0 - 8.4", "nette/utils": "^3.2.9 || ^4.0" }, "require-dev": { diff --git a/readme.md b/readme.md index 13d74d53..ba102dea 100644 --- a/readme.md +++ b/readme.md @@ -26,7 +26,7 @@ Download and install the library using the [Composer](https://doc.nette.org/en/b composer require nette/php-generator ``` -PhpGenerator 4.1 is compatible with PHP 8.0 to 8.3. Documentation can be found on the [library's website](https://doc.nette.org/php-generator). +PhpGenerator 4.1 is compatible with PHP 8.0 to 8.4. Documentation can be found on the [library's website](https://doc.nette.org/php-generator).   diff --git a/tests/PhpGenerator/fixtures/bodies.php b/tests/PhpGenerator/fixtures/bodies.php index 57133f2b..b13e695a 100644 --- a/tests/PhpGenerator/fixtures/bodies.php +++ b/tests/PhpGenerator/fixtures/bodies.php @@ -36,7 +36,7 @@ function long() throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant.'); } - function resolving($a = a\FOO, self $b = null, $c = self::FOO) + function resolving($a = a\FOO, ?self $b = null, $c = self::FOO) { // constants echo FOO; diff --git a/tests/PhpGenerator/fixtures/classes.php b/tests/PhpGenerator/fixtures/classes.php index 3a58dba7..e3d51960 100644 --- a/tests/PhpGenerator/fixtures/classes.php +++ b/tests/PhpGenerator/fixtures/classes.php @@ -72,7 +72,7 @@ private function &func3(/** foo */array $a, Class2 $b, \Abc\Unknown $c, \Xyz\Unk } - private function func4(array $a = [], Class2 $b = null, $c = Unknown::ABC) + private function func4(array $a = [], ?Class2 $b = null, $c = Unknown::ABC) { } @@ -97,7 +97,7 @@ class Class4 /** */ class Class5 { - public function func1(\A $a, ?\B $b, ?\C $c = null, \D $d = null, ?int $i = 1, ?array $arr = []) + public function func1(\A $a, ?\B $b, ?\C $c = null, ?\D $d = null, ?int $i = 1, ?array $arr = []) { } From e1e5e8fecde251763831e127ccba9a9c20676fe3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 18 Sep 2024 13:10:17 +0200 Subject: [PATCH 227/266] typo --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index ba102dea..a321aefb 100644 --- a/readme.md +++ b/readme.md @@ -387,11 +387,11 @@ $method->addParameter('items', []) // $items = [] // function count(&$items = []) ``` -To define the so-called variadics parameters (or also the splat, spread, ellipsis, unpacking or three dots operator), use `setVariadics()`: +To define the so-called variadics parameters (or also the splat, spread, ellipsis, unpacking or three dots operator), use `setVariadic()`: ```php $method = $class->addMethod('count'); -$method->setVariadics(true); +$method->setVariadic(true); $method->addParameter('items'); ``` From 9ad7883f84e96f8612d35a2e7d91cf12e135b860 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 28 Nov 2024 20:09:01 +0100 Subject: [PATCH 228/266] improved phpDoc --- src/PhpGenerator/Attribute.php | 2 +- src/PhpGenerator/ClassLike.php | 2 +- src/PhpGenerator/ClassType.php | 2 +- src/PhpGenerator/Closure.php | 2 +- src/PhpGenerator/Constant.php | 2 +- src/PhpGenerator/EnumCase.php | 2 +- src/PhpGenerator/EnumType.php | 2 +- src/PhpGenerator/GlobalFunction.php | 2 +- src/PhpGenerator/InterfaceType.php | 2 +- src/PhpGenerator/Method.php | 2 +- src/PhpGenerator/Parameter.php | 2 +- src/PhpGenerator/PhpFile.php | 2 +- src/PhpGenerator/PhpNamespace.php | 2 +- src/PhpGenerator/PromotedParameter.php | 2 +- src/PhpGenerator/Property.php | 2 +- src/PhpGenerator/TraitType.php | 2 +- src/PhpGenerator/TraitUse.php | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/PhpGenerator/Attribute.php b/src/PhpGenerator/Attribute.php index 4da227b4..896f9473 100644 --- a/src/PhpGenerator/Attribute.php +++ b/src/PhpGenerator/Attribute.php @@ -13,7 +13,7 @@ /** - * PHP Attribute. + * Definition of a PHP attribute. */ final class Attribute { diff --git a/src/PhpGenerator/ClassLike.php b/src/PhpGenerator/ClassLike.php index 7be48d8a..33a5be13 100644 --- a/src/PhpGenerator/ClassLike.php +++ b/src/PhpGenerator/ClassLike.php @@ -13,7 +13,7 @@ /** - * Class/Interface/Trait/Enum description. + * Base definition of class, interface, trait or enum type. */ abstract class ClassLike { diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 4e082685..8e04e24f 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -13,7 +13,7 @@ /** - * Class description. + * Definition of a class with properties, methods, constants, traits and PHP attributes. */ final class ClassType extends ClassLike { diff --git a/src/PhpGenerator/Closure.php b/src/PhpGenerator/Closure.php index 843feedb..49401e62 100644 --- a/src/PhpGenerator/Closure.php +++ b/src/PhpGenerator/Closure.php @@ -11,7 +11,7 @@ /** - * Closure. + * Definition of a closure. */ final class Closure { diff --git a/src/PhpGenerator/Constant.php b/src/PhpGenerator/Constant.php index 44c1938c..e18eff30 100644 --- a/src/PhpGenerator/Constant.php +++ b/src/PhpGenerator/Constant.php @@ -11,7 +11,7 @@ /** - * Class constant. + * Definition of a class constant. */ final class Constant { diff --git a/src/PhpGenerator/EnumCase.php b/src/PhpGenerator/EnumCase.php index 36bfd2fc..ee099b93 100644 --- a/src/PhpGenerator/EnumCase.php +++ b/src/PhpGenerator/EnumCase.php @@ -11,7 +11,7 @@ /** - * Enum case. + * Definition of an enum case. */ final class EnumCase { diff --git a/src/PhpGenerator/EnumType.php b/src/PhpGenerator/EnumType.php index 43456183..a4f47243 100644 --- a/src/PhpGenerator/EnumType.php +++ b/src/PhpGenerator/EnumType.php @@ -13,7 +13,7 @@ /** - * Enum description. + * Definition of an enum with cases, methods, constants and traits. */ final class EnumType extends ClassLike { diff --git a/src/PhpGenerator/GlobalFunction.php b/src/PhpGenerator/GlobalFunction.php index c6b1e973..c2ed0ac0 100644 --- a/src/PhpGenerator/GlobalFunction.php +++ b/src/PhpGenerator/GlobalFunction.php @@ -13,7 +13,7 @@ /** - * Global function. + * Definition of a global function. */ final class GlobalFunction { diff --git a/src/PhpGenerator/InterfaceType.php b/src/PhpGenerator/InterfaceType.php index 8743f8d0..c7044e83 100644 --- a/src/PhpGenerator/InterfaceType.php +++ b/src/PhpGenerator/InterfaceType.php @@ -13,7 +13,7 @@ /** - * Interface description. + * Definition of an interface with methods and constants. */ final class InterfaceType extends ClassLike { diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index 0e8e765b..adaea62b 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -13,7 +13,7 @@ /** - * Class method. + * Definition of a class method. */ final class Method { diff --git a/src/PhpGenerator/Parameter.php b/src/PhpGenerator/Parameter.php index 20651a31..910207ec 100644 --- a/src/PhpGenerator/Parameter.php +++ b/src/PhpGenerator/Parameter.php @@ -13,7 +13,7 @@ /** - * Function/Method parameter description. + * Definition of a function/method parameter. */ class Parameter { diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index 76b7947d..f38a10b3 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -11,7 +11,7 @@ /** - * Instance of PHP file. + * Definition of a PHP file. * * Generates: * - opening tag ( Date: Sat, 23 Nov 2024 20:42:59 +0100 Subject: [PATCH 229/266] Factory: fixed visibility of promoted property --- src/PhpGenerator/Factory.php | 11 ++++++++--- tests/PhpGenerator/expected/ClassType.from.81.expect | 2 +- tests/PhpGenerator/expected/ClassType.from.82.expect | 2 +- tests/PhpGenerator/expected/ClassType.from.expect | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index abc1f763..ec480fe7 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -199,9 +199,14 @@ public function fromCallable(callable $from): Method|GlobalFunction|Closure public function fromParameterReflection(\ReflectionParameter $from): Parameter { - $param = $from->isPromoted() - ? (new PromotedParameter($from->name))->setReadOnly(PHP_VERSION_ID >= 80100 && $from->getDeclaringClass()->getProperty($from->name)->isReadonly()) - : new Parameter($from->name); + if ($from->isPromoted()) { + $property = $from->getDeclaringClass()->getProperty($from->name); + $param = (new PromotedParameter($from->name)) + ->setVisibility($this->getVisibility($property)) + ->setReadOnly(PHP_VERSION_ID >= 80100 && $property->isReadonly()); + } else { + $param = new Parameter($from->name); + } $param->setReference($from->isPassedByReference()); $param->setType((string) $from->getType()); diff --git a/tests/PhpGenerator/expected/ClassType.from.81.expect b/tests/PhpGenerator/expected/ClassType.from.81.expect index 22b0d789..9d17ea27 100644 --- a/tests/PhpGenerator/expected/ClassType.from.81.expect +++ b/tests/PhpGenerator/expected/ClassType.from.81.expect @@ -28,7 +28,7 @@ class Class12 public function __construct( - public readonly string $foo, + private readonly string $foo, ) { } } diff --git a/tests/PhpGenerator/expected/ClassType.from.82.expect b/tests/PhpGenerator/expected/ClassType.from.82.expect index 894f1a20..fe1c6221 100644 --- a/tests/PhpGenerator/expected/ClassType.from.82.expect +++ b/tests/PhpGenerator/expected/ClassType.from.82.expect @@ -4,7 +4,7 @@ readonly class Class13 public function __construct( - public bool $bar = true, + private bool $bar = true, ) { } diff --git a/tests/PhpGenerator/expected/ClassType.from.expect b/tests/PhpGenerator/expected/ClassType.from.expect index 2ed08063..bcc1f5b7 100644 --- a/tests/PhpGenerator/expected/ClassType.from.expect +++ b/tests/PhpGenerator/expected/ClassType.from.expect @@ -111,7 +111,7 @@ class Class8 { public function __construct( public $a, - public string|int $b = 10, + private string|int $b = 10, $c = null, ) { } From e45595f2543818886ec93506234452ee00037bf8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 1 Nov 2024 12:57:50 +0100 Subject: [PATCH 230/266] Constant: type can be nullable --- src/PhpGenerator/Constant.php | 3 ++- tests/PhpGenerator/ClassType.phpt | 2 +- tests/PhpGenerator/expected/ClassType.expect | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/PhpGenerator/Constant.php b/src/PhpGenerator/Constant.php index e18eff30..1e89d6e6 100644 --- a/src/PhpGenerator/Constant.php +++ b/src/PhpGenerator/Constant.php @@ -53,7 +53,8 @@ public function isFinal(): bool public function setType(?string $type): static { - $this->type = Helpers::validateType($type); + Helpers::validateType($type); + $this->type = $type; return $this; } diff --git a/tests/PhpGenerator/ClassType.phpt b/tests/PhpGenerator/ClassType.phpt index a8e81eee..e22c3720 100644 --- a/tests/PhpGenerator/ClassType.phpt +++ b/tests/PhpGenerator/ClassType.phpt @@ -39,7 +39,7 @@ $trait2 = $class->addTrait('AnotherTrait') $class->addConstant('ROLE', 'admin'); $class->addConstant('ACTIVE', false) ->setFinal() - ->setType('bool'); + ->setType('?bool'); Assert::true($class->hasConstant('ROLE')); Assert::false($class->hasConstant('xxx')); diff --git a/tests/PhpGenerator/expected/ClassType.expect b/tests/PhpGenerator/expected/ClassType.expect index 80cef326..642dfb17 100644 --- a/tests/PhpGenerator/expected/ClassType.expect +++ b/tests/PhpGenerator/expected/ClassType.expect @@ -17,7 +17,7 @@ abstract class Example extends ParentClass implements IExample, IOne } public const ROLE = 'admin'; - final public const bool ACTIVE = false; + final public const ?bool ACTIVE = false; /** Commented */ private const FORCE_ARRAY = Nette\Utils\Json::FORCE_ARRAY; From 127d9762681b9f67b18d55cc400ff0f03134e63c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 1 Nov 2024 13:00:01 +0100 Subject: [PATCH 231/266] PsrPrinter is not final --- src/PhpGenerator/PsrPrinter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpGenerator/PsrPrinter.php b/src/PhpGenerator/PsrPrinter.php index f1b3b37b..5a889e63 100644 --- a/src/PhpGenerator/PsrPrinter.php +++ b/src/PhpGenerator/PsrPrinter.php @@ -13,7 +13,7 @@ /** * Generates PHP code compatible with PSR-2 and PSR-12. */ -final class PsrPrinter extends Printer +class PsrPrinter extends Printer { public string $indentation = ' '; public int $linesBetweenMethods = 1; From 5dab02e6e20447db20e71daa140053699b51fcd5 Mon Sep 17 00:00:00 2001 From: Nikolai Shangin Date: Thu, 21 Nov 2024 14:18:09 +0700 Subject: [PATCH 232/266] Printer: refactoring, added printFunctionBody() --- src/PhpGenerator/Printer.php | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 98d44982..f1941d10 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -46,8 +46,7 @@ public function printFunction(GlobalFunction $function, ?PhpNamespace $namespace . $function->getName(); $returnType = $this->printReturnType($function); $params = $this->printParameters($function, strlen($line) + strlen($returnType) + 2); // 2 = parentheses - $body = Helpers::simplifyTaggedNames($function->getBody(), $this->namespace); - $body = ltrim(rtrim(Strings::normalize($body)) . "\n"); + $body = $this->printFunctionBody($function); $braceOnNextLine = $this->isBraceOnNextLine(str_contains($params, "\n"), (bool) $returnType); return $this->printDocComment($function) @@ -71,8 +70,7 @@ public function printClosure(Closure $closure, ?PhpNamespace $namespace = null): $useStr = strlen($tmp = implode(', ', $uses)) > $this->wrapLength && count($uses) > 1 ? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . ",\n" : $tmp; - $body = Helpers::simplifyTaggedNames($closure->getBody(), $this->namespace); - $body = ltrim(rtrim(Strings::normalize($body)) . "\n"); + $body = $this->printFunctionBody($closure); return $this->printAttributes($closure->getAttributes(), inline: true) . 'function ' @@ -93,14 +91,14 @@ public function printArrowFunction(Closure $closure, ?PhpNamespace $namespace = } } - $body = Helpers::simplifyTaggedNames($closure->getBody(), $this->namespace); + $body = $this->printFunctionBody($closure); return $this->printAttributes($closure->getAttributes()) . 'fn' . ($closure->getReturnReference() ? '&' : '') . $this->printParameters($closure) . $this->printReturnType($closure) - . ' => ' . trim(Strings::normalize($body)) . ';'; + . ' => ' . rtrim($body, "\n") . ';'; } @@ -117,8 +115,7 @@ public function printMethod(Method $method, ?PhpNamespace $namespace = null, boo . $method->getName(); $returnType = $this->printReturnType($method); $params = $this->printParameters($method, strlen($line) + strlen($returnType) + strlen($this->indentation) + 2); - $body = Helpers::simplifyTaggedNames($method->getBody(), $this->namespace); - $body = ltrim(rtrim(Strings::normalize($body)) . "\n"); + $body = $this->printFunctionBody($method); $braceOnNextLine = $this->isBraceOnNextLine(str_contains($params, "\n"), (bool) $returnType); return $this->printDocComment($method) @@ -132,6 +129,14 @@ public function printMethod(Method $method, ?PhpNamespace $namespace = null, boo } + private function printFunctionBody(Closure|GlobalFunction|Method $function): string + { + $code = Helpers::simplifyTaggedNames($function->getBody(), $this->namespace); + $code = Strings::normalize($code); + return ltrim(rtrim($code) . "\n"); + } + + public function printClass( ClassType|InterfaceType|TraitType|EnumType $class, ?PhpNamespace $namespace = null, From 4628decb4b2dfce4e079fe7b6b389c255c6a6210 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 28 Nov 2024 19:40:28 +0100 Subject: [PATCH 233/266] ClassManipulator::implementInterface() renamed to implement() --- readme.md | 4 ++-- src/PhpGenerator/ClassManipulator.php | 20 +++++++++++++------ ...e.phpt => ClassManipulator.implement.phpt} | 4 ++-- 3 files changed, 18 insertions(+), 10 deletions(-) rename tests/PhpGenerator/{ClassManipulator.implementInterface.phpt => ClassManipulator.implement.phpt} (83%) diff --git a/readme.md b/readme.md index a321aefb..8082b48c 100644 --- a/readme.md +++ b/readme.md @@ -902,10 +902,10 @@ $property = $manipulator->inheritProperty('foo'); $property->setValue('new value'); ``` -The `implementInterface()` method automatically implements all methods from the given interface in your class: +The `implement()` method automatically implements all methods from the given interface in your class: ```php -$manipulator->implementInterface(SomeInterface::class); +$manipulator->implement(SomeInterface::class); // Now your class implements SomeInterface and includes all its methods ``` diff --git a/src/PhpGenerator/ClassManipulator.php b/src/PhpGenerator/ClassManipulator.php index d97ee319..f212675e 100644 --- a/src/PhpGenerator/ClassManipulator.php +++ b/src/PhpGenerator/ClassManipulator.php @@ -80,16 +80,24 @@ public function inheritMethod(string $name, bool $returnIfExists = false): Metho /** * Implements all methods from the given interface. */ - public function implementInterface(string $interfaceName): void + public function implement(string $name): void { - $interface = new \ReflectionClass($interfaceName); - if (!$interface->isInterface()) { - throw new Nette\InvalidArgumentException("Class '$interfaceName' is not an interface."); + $definition = new \ReflectionClass($name); + if (!$definition->isInterface()) { + throw new Nette\InvalidArgumentException("Class '$name' is not an interface."); } - $this->class->addImplement($interfaceName); - foreach ($interface->getMethods() as $method) { + $this->class->addImplement($name); + foreach ($definition->getMethods() as $method) { $this->inheritMethod($method->getName(), returnIfExists: true); } } + + + /** @deprecated use implement() */ + public function implementInterface(string $interfaceName): void + { + trigger_error(__METHOD__ . '() is deprecated, use implement()', E_USER_DEPRECATED); + $this->implement($interfaceName); + } } diff --git a/tests/PhpGenerator/ClassManipulator.implementInterface.phpt b/tests/PhpGenerator/ClassManipulator.implement.phpt similarity index 83% rename from tests/PhpGenerator/ClassManipulator.implementInterface.phpt rename to tests/PhpGenerator/ClassManipulator.implement.phpt index a0e18e52..c3a5c930 100644 --- a/tests/PhpGenerator/ClassManipulator.implementInterface.phpt +++ b/tests/PhpGenerator/ClassManipulator.implement.phpt @@ -18,12 +18,12 @@ $class = new ClassType('TestClass'); $manipulator = new ClassManipulator($class); // Test valid interface implementation -$manipulator->implementInterface(TestInterface::class); +$manipulator->implement(TestInterface::class); Assert::true(in_array(TestInterface::class, $class->getImplements(), true)); Assert::true($class->hasMethod('testMethod')); // Test exception for non-interface Assert::exception( - fn() => $manipulator->implementInterface(stdClass::class), + fn() => $manipulator->implement(stdClass::class), InvalidArgumentException::class, ); From 09d7a2bfaf3536b678fc97e12baec0677832c21f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 28 Nov 2024 20:48:25 +0100 Subject: [PATCH 234/266] ClassManipulator::implement() can implement abstract classes --- readme.md | 2 +- src/PhpGenerator/ClassManipulator.php | 28 +++++++--- .../ClassManipulator.implement.phpt | 55 +++++++++++++++++-- 3 files changed, 70 insertions(+), 15 deletions(-) diff --git a/readme.md b/readme.md index 8082b48c..5e8c8c95 100644 --- a/readme.md +++ b/readme.md @@ -902,7 +902,7 @@ $property = $manipulator->inheritProperty('foo'); $property->setValue('new value'); ``` -The `implement()` method automatically implements all methods from the given interface in your class: +The `implement()` method automatically implements all methods and properties from the given interface or abstract class: ```php $manipulator->implement(SomeInterface::class); diff --git a/src/PhpGenerator/ClassManipulator.php b/src/PhpGenerator/ClassManipulator.php index f212675e..b0d3b507 100644 --- a/src/PhpGenerator/ClassManipulator.php +++ b/src/PhpGenerator/ClassManipulator.php @@ -68,9 +68,7 @@ public function inheritMethod(string $name, bool $returnIfExists = false): Metho } catch (\ReflectionException) { continue; } - $method = (new Factory)->fromMethodReflection($rm); - $this->class->addMember($method); - return $method; + return $this->implementMethod($rm); } throw new Nette\InvalidStateException("Method '$name' has not been found in any ancestor: " . implode(', ', $parents)); @@ -78,22 +76,36 @@ public function inheritMethod(string $name, bool $returnIfExists = false): Metho /** - * Implements all methods from the given interface. + * Implements all methods from the given interface or abstract class. */ public function implement(string $name): void { $definition = new \ReflectionClass($name); - if (!$definition->isInterface()) { - throw new Nette\InvalidArgumentException("Class '$name' is not an interface."); + if ($definition->isInterface()) { + $this->class->addImplement($name); + } elseif ($definition->isAbstract()) { + $this->class->setExtends($name); + } else { + throw new Nette\InvalidArgumentException("'$name' is not an interface or abstract class."); } - $this->class->addImplement($name); foreach ($definition->getMethods() as $method) { - $this->inheritMethod($method->getName(), returnIfExists: true); + if (!$this->class->hasMethod($method->getName()) && $method->isAbstract()) { + $this->implementMethod($method); + } } } + private function implementMethod(\ReflectionMethod $rm): Method + { + $method = (new Factory)->fromMethodReflection($rm); + $method->setAbstract(false); + $this->class->addMember($method); + return $method; + } + + /** @deprecated use implement() */ public function implementInterface(string $interfaceName): void { diff --git a/tests/PhpGenerator/ClassManipulator.implement.phpt b/tests/PhpGenerator/ClassManipulator.implement.phpt index c3a5c930..4558d591 100644 --- a/tests/PhpGenerator/ClassManipulator.implement.phpt +++ b/tests/PhpGenerator/ClassManipulator.implement.phpt @@ -9,21 +9,64 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -interface TestInterface +interface ParentInterface { - public function testMethod(); + public function interfaceMethod(); } +interface TestInterface extends ParentInterface +{ +} + +abstract class ParentAbstract +{ + abstract public function abstractMethod(); + + + public function concreteMethod() + { + } +} + +abstract class TestAbstract extends ParentAbstract +{ +} + + $class = new ClassType('TestClass'); $manipulator = new ClassManipulator($class); -// Test valid interface implementation +// Test interface implementation $manipulator->implement(TestInterface::class); -Assert::true(in_array(TestInterface::class, $class->getImplements(), true)); -Assert::true($class->hasMethod('testMethod')); +Assert::match(<<<'XX' + class TestClass implements TestInterface + { + function interfaceMethod() + { + } + } + + XX, (string) $class); + + +// Test abstract class extension +$class = new ClassType('TestClass'); +$manipulator = new ClassManipulator($class); +$manipulator->implement(TestAbstract::class); +Assert::match(<<<'XX' + class TestClass extends TestAbstract + { + public function abstractMethod() + { + } + } + + XX, (string) $class); + -// Test exception for non-interface +// Test exception for regular class Assert::exception( fn() => $manipulator->implement(stdClass::class), InvalidArgumentException::class, + "'stdClass' is not an interface or abstract class.", ); From a5711b37c40939faa67915c657f7599c5c45bd65 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 13 Oct 2023 20:59:11 +0200 Subject: [PATCH 235/266] added Visibility --- src/PhpGenerator/ClassLike.php | 20 ++++++------ src/PhpGenerator/Extractor.php | 6 ++-- src/PhpGenerator/Factory.php | 4 +-- src/PhpGenerator/Method.php | 2 +- src/PhpGenerator/Traits/VisibilityAware.php | 27 ++++++---------- src/PhpGenerator/Visibility.php | 34 +++++++++++++++++++++ tests/PhpGenerator/ClassType.phpt | 3 +- 7 files changed, 60 insertions(+), 36 deletions(-) create mode 100644 src/PhpGenerator/Visibility.php diff --git a/src/PhpGenerator/ClassLike.php b/src/PhpGenerator/ClassLike.php index 33a5be13..a5ad5e8f 100644 --- a/src/PhpGenerator/ClassLike.php +++ b/src/PhpGenerator/ClassLike.php @@ -20,19 +20,17 @@ abstract class ClassLike use Traits\CommentAware; use Traits\AttributeAware; - public const - VisibilityPublic = 'public', - VisibilityProtected = 'protected', - VisibilityPrivate = 'private'; + /** @deprecated use Visibility::Public */ + public const VisibilityPublic = Visibility::Public, + VISIBILITY_PUBLIC = Visibility::Public; - /** @deprecated use ClassLike::VisibilityPublic */ - public const VISIBILITY_PUBLIC = self::VisibilityPublic; + /** @deprecated use Visibility::Protected */ + public const VisibilityProtected = Visibility::Protected, + VISIBILITY_PROTECTED = Visibility::Protected; - /** @deprecated use ClassLike::VisibilityProtected */ - public const VISIBILITY_PROTECTED = self::VisibilityProtected; - - /** @deprecated use ClassLike::VisibilityPrivate */ - public const VISIBILITY_PRIVATE = self::VisibilityPrivate; + /** @deprecated use Visibility::Private */ + public const VisibilityPrivate = Visibility::Private, + VISIBILITY_PRIVATE = Visibility::Private; private ?PhpNamespace $namespace; private ?string $name; diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 5d39905e..68127f49 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -483,9 +483,9 @@ private function toValue(Node\Expr $node): mixed private function toVisibility(int $flags): ?string { return match (true) { - (bool) ($flags & Node\Stmt\Class_::MODIFIER_PUBLIC) => ClassType::VisibilityPublic, - (bool) ($flags & Node\Stmt\Class_::MODIFIER_PROTECTED) => ClassType::VisibilityProtected, - (bool) ($flags & Node\Stmt\Class_::MODIFIER_PRIVATE) => ClassType::VisibilityPrivate, + (bool) ($flags & Node\Stmt\Class_::MODIFIER_PUBLIC) => Visibility::Public, + (bool) ($flags & Node\Stmt\Class_::MODIFIER_PROTECTED) => Visibility::Protected, + (bool) ($flags & Node\Stmt\Class_::MODIFIER_PRIVATE) => Visibility::Private, default => null, }; } diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index ec480fe7..22a45f7b 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -307,8 +307,8 @@ private function getAttributes($from): array private function getVisibility($from): string { return $from->isPrivate() - ? ClassLike::VisibilityPrivate - : ($from->isProtected() ? ClassLike::VisibilityProtected : ClassLike::VisibilityPublic); + ? Visibility::Private + : ($from->isProtected() ? Visibility::Protected : Visibility::Public); } diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index adaea62b..92b8e00b 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -98,7 +98,7 @@ public function addPromotedParameter(string $name, mixed $defaultValue = null): /** @throws Nette\InvalidStateException */ public function validate(): void { - if ($this->abstract && ($this->final || $this->visibility === ClassLike::VisibilityPrivate)) { + if ($this->abstract && ($this->final || $this->visibility === Visibility::Private)) { throw new Nette\InvalidStateException("Method $this->name() cannot be abstract and final or private at the same time."); } } diff --git a/src/PhpGenerator/Traits/VisibilityAware.php b/src/PhpGenerator/Traits/VisibilityAware.php index 0b3230ed..0a3a383f 100644 --- a/src/PhpGenerator/Traits/VisibilityAware.php +++ b/src/PhpGenerator/Traits/VisibilityAware.php @@ -9,8 +9,7 @@ namespace Nette\PhpGenerator\Traits; -use Nette; -use Nette\PhpGenerator\ClassLike; +use Nette\PhpGenerator\Visibility; /** @@ -22,16 +21,10 @@ trait VisibilityAware private ?string $visibility = null; - /** - * @param string|null $val public|protected|private - */ - public function setVisibility(?string $val): static + /** @param 'public'|'protected'|'private'|null $value */ + public function setVisibility(?string $value): static { - if (!in_array($val, [ClassLike::VisibilityPublic, ClassLike::VisibilityProtected, ClassLike::VisibilityPrivate, null], true)) { - throw new Nette\InvalidArgumentException('Argument must be public|protected|private.'); - } - - $this->visibility = $val; + $this->visibility = $value === null ? $value : Visibility::from($value); return $this; } @@ -44,39 +37,39 @@ public function getVisibility(): ?string public function setPublic(): static { - $this->visibility = ClassLike::VisibilityPublic; + $this->visibility = Visibility::Public; return $this; } public function isPublic(): bool { - return $this->visibility === ClassLike::VisibilityPublic || $this->visibility === null; + return $this->visibility === Visibility::Public || $this->visibility === null; } public function setProtected(): static { - $this->visibility = ClassLike::VisibilityProtected; + $this->visibility = Visibility::Protected; return $this; } public function isProtected(): bool { - return $this->visibility === ClassLike::VisibilityProtected; + return $this->visibility === Visibility::Protected; } public function setPrivate(): static { - $this->visibility = ClassLike::VisibilityPrivate; + $this->visibility = Visibility::Private; return $this; } public function isPrivate(): bool { - return $this->visibility === ClassLike::VisibilityPrivate; + return $this->visibility === Visibility::Private; } } diff --git a/src/PhpGenerator/Visibility.php b/src/PhpGenerator/Visibility.php new file mode 100644 index 00000000..a31c75c8 --- /dev/null +++ b/src/PhpGenerator/Visibility.php @@ -0,0 +1,34 @@ +getParameters()); Assert::exception( fn() => (new ClassType)->addMethod('method')->setVisibility('unknown'), - Nette\InvalidArgumentException::class, - 'Argument must be public|protected|private.', + ValueError::class, ); From bad08ce3fc817f7976b44b270a8b693b139e07b3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 23 Nov 2024 18:25:05 +0100 Subject: [PATCH 236/266] added PropertyLike --- src/PhpGenerator/PromotedParameter.php | 18 +---- src/PhpGenerator/Property.php | 16 +--- src/PhpGenerator/Traits/PropertyLike.php | 35 +++++++++ .../PhpGenerator/PropertyLike.visiblity.phpt | 78 +++++++++++++++++++ 4 files changed, 115 insertions(+), 32 deletions(-) create mode 100644 src/PhpGenerator/Traits/PropertyLike.php create mode 100644 tests/PhpGenerator/PropertyLike.visiblity.phpt diff --git a/src/PhpGenerator/PromotedParameter.php b/src/PhpGenerator/PromotedParameter.php index 701d0864..85240147 100644 --- a/src/PhpGenerator/PromotedParameter.php +++ b/src/PhpGenerator/PromotedParameter.php @@ -17,23 +17,7 @@ */ final class PromotedParameter extends Parameter { - use Traits\VisibilityAware; - - private bool $readOnly = false; - - - public function setReadOnly(bool $state = true): static - { - $this->readOnly = $state; - return $this; - } - - - public function isReadOnly(): bool - { - return $this->readOnly; - } - + use Traits\PropertyLike; /** @throws Nette\InvalidStateException */ public function validate(): void diff --git a/src/PhpGenerator/Property.php b/src/PhpGenerator/Property.php index 68dff843..f30739a7 100644 --- a/src/PhpGenerator/Property.php +++ b/src/PhpGenerator/Property.php @@ -19,7 +19,7 @@ final class Property { use Traits\NameAware; - use Traits\VisibilityAware; + use Traits\PropertyLike; use Traits\CommentAware; use Traits\AttributeAware; @@ -28,7 +28,6 @@ final class Property private ?string $type = null; private bool $nullable = false; private bool $initialized = false; - private bool $readOnly = false; public function setValue(mixed $val): static @@ -100,19 +99,6 @@ public function isInitialized(): bool } - public function setReadOnly(bool $state = true): static - { - $this->readOnly = $state; - return $this; - } - - - public function isReadOnly(): bool - { - return $this->readOnly; - } - - /** @throws Nette\InvalidStateException */ public function validate(): void { diff --git a/src/PhpGenerator/Traits/PropertyLike.php b/src/PhpGenerator/Traits/PropertyLike.php new file mode 100644 index 00000000..5f7b528a --- /dev/null +++ b/src/PhpGenerator/Traits/PropertyLike.php @@ -0,0 +1,35 @@ +readOnly = $state; + return $this; + } + + + public function isReadOnly(): bool + { + return $this->readOnly; + } +} diff --git a/tests/PhpGenerator/PropertyLike.visiblity.phpt b/tests/PhpGenerator/PropertyLike.visiblity.phpt new file mode 100644 index 00000000..fbb29720 --- /dev/null +++ b/tests/PhpGenerator/PropertyLike.visiblity.phpt @@ -0,0 +1,78 @@ +addProperty('first') + ->setType('string'); +Assert::true($default->isPublic()); +Assert::false($default->isProtected()); +Assert::false($default->isPrivate()); +Assert::null($default->getVisibility()); + +// Explicit public +$public = $class->addProperty('second') + ->setType('string') + ->setPublic(); +Assert::true($public->isPublic()); +Assert::false($public->isProtected()); +Assert::false($public->isPrivate()); +Assert::same('public', $public->getVisibility()); + +// Protected +$protected = $class->addProperty('third') + ->setType('string') + ->setProtected(); +Assert::false($protected->isPublic()); +Assert::true($protected->isProtected()); +Assert::false($protected->isPrivate()); +Assert::same('protected', $protected->getVisibility()); + +// Private +$private = $class->addProperty('fourth') + ->setType('string') + ->setPrivate(); +Assert::false($private->isPublic()); +Assert::false($private->isProtected()); +Assert::true($private->isPrivate()); +Assert::same('private', $private->getVisibility()); + +// Change visibility +$changing = $class->addProperty('fifth') + ->setType('string') + ->setPublic(); +$changing->setVisibility('protected'); +Assert::false($changing->isPublic()); +Assert::true($changing->isProtected()); +Assert::false($changing->isPrivate()); + +// Test invalid visibility +Assert::exception( + fn() => $changing->setVisibility('invalid'), + Nette\InvalidArgumentException::class, + 'Argument must be public|protected|private.', +); + +same(<<<'XX' + class Demo + { + public string $first; + public string $second; + protected string $third; + private string $fourth; + protected string $fifth; + } + + XX, (string) $class); From 5126fb876edfeae3bb8506549ae80b7024873feb Mon Sep 17 00:00:00 2001 From: Nikolai Shangin Date: Wed, 27 Nov 2024 17:17:43 +0100 Subject: [PATCH 237/266] added support for properties hooks [Closes #171] --- composer.json | 2 +- readme.md | 38 +++++- src/PhpGenerator/Printer.php | 44 +++++-- src/PhpGenerator/PromotedParameter.php | 6 + src/PhpGenerator/Property.php | 6 + src/PhpGenerator/PropertyHook.php | 109 ++++++++++++++++++ src/PhpGenerator/PropertyHookType.php | 33 ++++++ src/PhpGenerator/Traits/PropertyLike.php | 46 ++++++++ tests/PhpGenerator/PropertyLike.hooks.phpt | 97 ++++++++++++++++ .../PhpGenerator/PropertyLike.visiblity.phpt | 3 +- 10 files changed, 372 insertions(+), 12 deletions(-) create mode 100644 src/PhpGenerator/PropertyHook.php create mode 100644 src/PhpGenerator/PropertyHookType.php create mode 100644 tests/PhpGenerator/PropertyLike.hooks.phpt diff --git a/composer.json b/composer.json index c7026efc..cdd26cf6 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.3 features.", + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.4 features.", "keywords": ["nette", "php", "code", "scaffolding"], "homepage": "https://nette.org", "license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"], diff --git a/readme.md b/readme.md index 5e8c8c95..4b34e3b1 100644 --- a/readme.md +++ b/readme.md @@ -8,7 +8,7 @@ Are you looking for a tool to generate PHP code for [classes](#classes), [functi

-✅ Supports all the latest PHP features like [enums](#enums), [attributes](#attributes), etc.
+✅ Supports all the latest PHP features like [property hooks](#property-hooks), [enums](#enums), [attributes](#attributes), etc.
✅ Allows you to easily modify [existing classes](#generating-from-existing-ones)
✅ Output compliant with [PSR-12 / PER coding style](#printer-and-psr-compliance)
✅ Highly mature, stable, and widely used library @@ -665,6 +665,42 @@ class Demo   +Property Hooks +-------------- + +You can also define property hooks (represented by the class [PropertyHook](https://api.nette.org/php-generator/master/Nette/PhpGenerator/PropertyHook.html)) for get and set operations, a feature introduced in PHP 8.4: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$prop = $class->addProperty('firstName') + ->setType('string'); + +$prop->addHook('set', 'strtolower($value)') + ->addParameter('value') + ->setType('string'); + +$prop->addHook('get') + ->setBody('return ucfirst($this->firstName);'); + +echo $class; +``` + +This generates: + +```php +class Demo +{ + public string $firstName { + set(string $value) => strtolower($value); + get { + return ucfirst($this->firstName); + } + } +} +``` + +  + Namespace --------- diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index f1941d10..ee22c9b4 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -129,7 +129,7 @@ public function printMethod(Method $method, ?PhpNamespace $namespace = null, boo } - private function printFunctionBody(Closure|GlobalFunction|Method $function): string + private function printFunctionBody(Closure|GlobalFunction|Method|PropertyHook $function): string { $code = Helpers::simplifyTaggedNames($function->getBody(), $this->namespace); $code = Strings::normalize($code); @@ -313,7 +313,7 @@ protected function printUses(PhpNamespace $namespace, string $of = PhpNamespace: } - protected function printParameters(Closure|GlobalFunction|Method $function, int $column = 0): string + protected function printParameters(Closure|GlobalFunction|Method|PropertyHook $function, int $column = 0): string { $special = false; foreach ($function->getParameters() as $param) { @@ -332,13 +332,13 @@ protected function printParameters(Closure|GlobalFunction|Method $function, int } - private function formatParameters(Closure|GlobalFunction|Method $function, bool $multiline): string + private function formatParameters(Closure|GlobalFunction|Method|PropertyHook $function, bool $multiline): string { $params = $function->getParameters(); $res = ''; foreach ($params as $param) { - $variadic = $function->isVariadic() && $param === end($params); + $variadic = !$function instanceof PropertyHook && $function->isVariadic() && $param === end($params); $attrs = $this->printAttributes($param->getAttributes(), inline: true); $res .= $this->printDocComment($param) @@ -351,6 +351,7 @@ private function formatParameters(Closure|GlobalFunction|Method $function, bool . ($variadic ? '...' : '') . '$' . $param->getName() . ($param->hasDefaultValue() && !$variadic ? ' = ' . $this->dump($param->getDefaultValue()) : '') + . ($param instanceof PromotedParameter ? $this->printHooks($param) : '') . ($multiline ? ",\n" : ', '); } @@ -386,13 +387,16 @@ private function printProperty(Property $property, bool $readOnlyClass = false): . ltrim($this->printType($type, $property->isNullable()) . ' ') . '$' . $property->getName()); + $defaultValue = $property->getValue() === null && !$property->isInitialized() + ? '' + : ' = ' . $this->dump($property->getValue(), strlen($def) + 3); // 3 = ' = ' + return $this->printDocComment($property) . $this->printAttributes($property->getAttributes()) . $def - . ($property->getValue() === null && !$property->isInitialized() - ? '' - : ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = ' - . ";\n"; + . $defaultValue + . ($this->printHooks($property) ?: ';') + . "\n"; } @@ -452,6 +456,30 @@ protected function printAttributes(array $attrs, bool $inline = false): string } + private function printHooks(Property|PromotedParameter $property): string + { + $hooks = $property->getHooks(); + if (!$hooks) { + return ''; + } + + foreach ($property->getHooks() as $type => $hook) { + $hooks[$type] = $this->printDocComment($hook) + . $this->printAttributes($hook->getAttributes()) + . ($hook->isFinal() ? 'final ' : '') + . ($hook->getReturnReference() ? '&' : '') + . $type + . ($hook->getParameters() ? $this->printParameters($hook) : '') + . ' ' + . ($hook->isShort() + ? '=> ' . $hook->getBody() . ';' + : "{\n" . $this->indent($this->printFunctionBody($hook)) . '}'); + } + + return " {\n" . $this->indent(implode("\n", $hooks)) . "\n}"; + } + + public function setTypeResolving(bool $state = true): static { $this->resolveTypes = $state; diff --git a/src/PhpGenerator/PromotedParameter.php b/src/PhpGenerator/PromotedParameter.php index 85240147..f81764ae 100644 --- a/src/PhpGenerator/PromotedParameter.php +++ b/src/PhpGenerator/PromotedParameter.php @@ -26,4 +26,10 @@ public function validate(): void throw new Nette\InvalidStateException("Property \${$this->getName()}: Read-only properties are only supported on typed property."); } } + + + public function __clone(): void + { + $this->hooks = array_map(fn($item) => $item ? clone $item : $item, $this->hooks); + } } diff --git a/src/PhpGenerator/Property.php b/src/PhpGenerator/Property.php index f30739a7..c627e36a 100644 --- a/src/PhpGenerator/Property.php +++ b/src/PhpGenerator/Property.php @@ -106,4 +106,10 @@ public function validate(): void throw new Nette\InvalidStateException("Property \$$this->name: Read-only properties are only supported on typed property."); } } + + + public function __clone(): void + { + $this->hooks = array_map(fn($item) => $item ? clone $item : $item, $this->hooks); + } } diff --git a/src/PhpGenerator/PropertyHook.php b/src/PhpGenerator/PropertyHook.php new file mode 100644 index 00000000..3c831fd4 --- /dev/null +++ b/src/PhpGenerator/PropertyHook.php @@ -0,0 +1,109 @@ +body = $args === null + ? $code + : (new Dumper)->format($code, ...$args); + $this->short = $short; + return $this; + } + + + public function getBody(): string + { + return $this->body; + } + + + public function isShort(): bool + { + return $this->short && trim($this->body) !== ''; + } + + + public function setFinal(bool $state = true): static + { + $this->final = $state; + return $this; + } + + + public function isFinal(): bool + { + return $this->final; + } + + + /** @internal */ + public function setParameters(array $val): static + { + (function (Parameter ...$val) {})(...$val); + $this->parameters = []; + foreach ($val as $v) { + $this->parameters[$v->getName()] = $v; + } + + return $this; + } + + + /** @internal */ + public function getParameters(): array + { + return $this->parameters; + } + + + /** + * Adds a parameter. If it already exists, it overwrites it. + * @param string $name without $ + */ + public function addParameter(string $name): Parameter + { + return $this->parameters[$name] = new Parameter($name); + } + + + public function setReturnReference(bool $state = true): static + { + $this->returnReference = $state; + return $this; + } + + + public function getReturnReference(): bool + { + return $this->returnReference; + } +} diff --git a/src/PhpGenerator/PropertyHookType.php b/src/PhpGenerator/PropertyHookType.php new file mode 100644 index 00000000..ca23532f --- /dev/null +++ b/src/PhpGenerator/PropertyHookType.php @@ -0,0 +1,33 @@ + */ + private array $hooks = [PropertyHookType::Set => null, PropertyHookType::Get => null]; + public function setReadOnly(bool $state = true): static { @@ -32,4 +37,45 @@ public function isReadOnly(): bool { return $this->readOnly; } + + + /** + * Replaces all hooks. + * @param PropertyHook[] $hooks + */ + public function setHooks(array $hooks): static + { + (function (PropertyHook ...$hooks) {})(...$hooks); + $this->hooks = $hooks; + return $this; + } + + + /** @return array */ + public function getHooks(): array + { + return array_filter($this->hooks); + } + + + /** @param 'set'|'get' $type */ + public function addHook(string $type, string $shortBody = ''): PropertyHook + { + return $this->hooks[PropertyHookType::from($type)] = (new PropertyHook) + ->setBody($shortBody, short: true); + } + + + /** @param 'set'|'get' $type */ + public function getHook(string $type): ?PropertyHook + { + return $this->hooks[PropertyHookType::from($type)] ?? null; + } + + + /** @param 'set'|'get' $type */ + public function hasHook(string $type): bool + { + return isset($this->hooks[PropertyHookType::from($type)]); + } } diff --git a/tests/PhpGenerator/PropertyLike.hooks.phpt b/tests/PhpGenerator/PropertyLike.hooks.phpt new file mode 100644 index 00000000..bfdfbf4e --- /dev/null +++ b/tests/PhpGenerator/PropertyLike.hooks.phpt @@ -0,0 +1,97 @@ +addProperty('first') + ->setType('string') + ->setValue('x') + ->setPublic() + ->addHook(PropertyHookType::Set) + ->setBody('$value . ?', ['x'], short: true) + ->addComment('comment') + ->addAttribute('Example') + ->addParameter('value') + ->setType('string'); + +$prop = $class->addProperty('second') + ->setType('string') + ->setPublic(); + +$prop->addHook('get') + ->setBody('return $this->second;') + ->setReturnReference() + ->setFinal(); + +$prop->addHook('set', '$value') + ->addParameter('value') + ->setType('string'); + +same(<<<'XX' + class Demo + { + public string $first = 'x' { + /** comment */ + #[Example] + set(string $value) => $value . 'x'; + } + + public string $second { + set(string $value) => $value; + final &get { + return $this->second; + } + } + } + + XX, (string) $class); + + + +// promoted properties + +$class = new ClassType('Demo'); + +$method = $class->addMethod('__construct'); + +$method->addPromotedParameter('first') + ->setType('string') + ->addHook('get') + ->setBody('return $this->first . "x";') + ->setReturnReference(); + +$method->addPromotedParameter('second') + ->setType('string') + ->addHook('set', '$value') + ->setFinal() + ->addParameter('value') + ->setType('string'); + +same(<<<'XX' + class Demo + { + public function __construct( + public string $first { + &get { + return $this->first . "x"; + } + }, + public string $second { + final set(string $value) => $value; + }, + ) { + } + } + + XX, (string) $class); diff --git a/tests/PhpGenerator/PropertyLike.visiblity.phpt b/tests/PhpGenerator/PropertyLike.visiblity.phpt index fbb29720..3d867171 100644 --- a/tests/PhpGenerator/PropertyLike.visiblity.phpt +++ b/tests/PhpGenerator/PropertyLike.visiblity.phpt @@ -61,8 +61,7 @@ Assert::false($changing->isPrivate()); // Test invalid visibility Assert::exception( fn() => $changing->setVisibility('invalid'), - Nette\InvalidArgumentException::class, - 'Argument must be public|protected|private.', + ValueError::class, ); same(<<<'XX' From 466918dca2c682da667d01a5273171c482deab79 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 29 Nov 2024 02:19:51 +0100 Subject: [PATCH 238/266] Interfaces can have properties --- src/PhpGenerator/ClassManipulator.php | 46 +++++++---- src/PhpGenerator/InterfaceType.php | 20 ++++- src/PhpGenerator/Printer.php | 35 ++++---- src/PhpGenerator/PropertyHook.php | 14 ++++ .../ClassManipulator.implement.84.phpt | 81 +++++++++++++++++++ .../ClassManipulator.implement.phpt | 3 + .../ClassManipulator.inheritProperty.phpt | 4 +- .../PhpGenerator/InterfaceType.validate.phpt | 21 +++++ tests/PhpGenerator/PropertyLike.hooks.phpt | 25 ++++++ 9 files changed, 216 insertions(+), 33 deletions(-) create mode 100644 tests/PhpGenerator/ClassManipulator.implement.84.phpt create mode 100644 tests/PhpGenerator/InterfaceType.validate.phpt diff --git a/src/PhpGenerator/ClassManipulator.php b/src/PhpGenerator/ClassManipulator.php index b0d3b507..48ca7ef0 100644 --- a/src/PhpGenerator/ClassManipulator.php +++ b/src/PhpGenerator/ClassManipulator.php @@ -25,25 +25,25 @@ public function __construct( */ public function inheritProperty(string $name, bool $returnIfExists = false): Property { - $extends = $this->class->getExtends(); if ($this->class->hasProperty($name)) { return $returnIfExists ? $this->class->getProperty($name) : throw new Nette\InvalidStateException("Cannot inherit property '$name', because it already exists."); - - } elseif (!$extends) { - throw new Nette\InvalidStateException("Class '{$this->class->getName()}' has not setExtends() set."); } - try { - $rp = new \ReflectionProperty($extends, $name); - } catch (\ReflectionException) { - throw new Nette\InvalidStateException("Property '$name' has not been found in ancestor {$extends}"); + $parents = [...(array) $this->class->getExtends(), ...$this->class->getImplements()] + ?: throw new Nette\InvalidStateException("Class '{$this->class->getName()}' has neither setExtends() nor setImplements() set."); + + foreach ($parents as $parent) { + try { + $rp = new \ReflectionProperty($parent, $name); + } catch (\ReflectionException) { + continue; + } + return $this->implementProperty($rp); } - $property = (new Factory)->fromPropertyReflection($rp); - $this->class->addMember($property); - return $property; + throw new Nette\InvalidStateException("Property '$name' has not been found in any ancestor: " . implode(', ', $parents)); } @@ -52,16 +52,15 @@ public function inheritProperty(string $name, bool $returnIfExists = false): Pro */ public function inheritMethod(string $name, bool $returnIfExists = false): Method { - $parents = [...(array) $this->class->getExtends(), ...$this->class->getImplements()]; if ($this->class->hasMethod($name)) { return $returnIfExists ? $this->class->getMethod($name) : throw new Nette\InvalidStateException("Cannot inherit method '$name', because it already exists."); - - } elseif (!$parents) { - throw new Nette\InvalidStateException("Class '{$this->class->getName()}' has neither setExtends() nor setImplements() set."); } + $parents = [...(array) $this->class->getExtends(), ...$this->class->getImplements()] + ?: throw new Nette\InvalidStateException("Class '{$this->class->getName()}' has neither setExtends() nor setImplements() set."); + foreach ($parents as $parent) { try { $rm = new \ReflectionMethod($parent, $name); @@ -94,6 +93,14 @@ public function implement(string $name): void $this->implementMethod($method); } } + + if (PHP_VERSION_ID >= 80400) { + foreach ($definition->getProperties() as $property) { + if (!$this->class->hasProperty($property->getName()) && $property->isAbstract()) { + $this->implementProperty($property); + } + } + } } @@ -106,6 +113,15 @@ private function implementMethod(\ReflectionMethod $rm): Method } + private function implementProperty(\ReflectionProperty $rp): Property + { + $property = (new Factory)->fromPropertyReflection($rp); + $property->setHooks([]); + $this->class->addMember($property); + return $property; + } + + /** @deprecated use implement() */ public function implementInterface(string $interfaceName): void { diff --git a/src/PhpGenerator/InterfaceType.php b/src/PhpGenerator/InterfaceType.php index c7044e83..3d179d25 100644 --- a/src/PhpGenerator/InterfaceType.php +++ b/src/PhpGenerator/InterfaceType.php @@ -13,12 +13,13 @@ /** - * Definition of an interface with methods and constants. + * Definition of an interface with properties, methods and constants. */ final class InterfaceType extends ClassLike { use Traits\ConstantsAware; use Traits\MethodsAware; + use Traits\PropertiesAware; /** @var string[] */ private array $extends = []; @@ -54,12 +55,13 @@ public function addExtend(string $name): static /** * Adds a member. If it already exists, throws an exception or overwrites it if $overwrite is true. */ - public function addMember(Method|Constant $member, bool $overwrite = false): static + public function addMember(Method|Constant|Property $member, bool $overwrite = false): static { $name = $member->getName(); [$type, $n] = match (true) { $member instanceof Constant => ['consts', $name], $member instanceof Method => ['methods', strtolower($name)], + $member instanceof Property => ['properties', $name], }; if (!$overwrite && isset($this->$type[$n])) { throw new Nette\InvalidStateException("Cannot add member '$name', because it already exists."); @@ -69,11 +71,25 @@ public function addMember(Method|Constant $member, bool $overwrite = false): sta } + /** @throws Nette\InvalidStateException */ + public function validate(): void + { + foreach ($this->getProperties() as $property) { + if ($property->isInitialized()) { + throw new Nette\InvalidStateException("Property {$this->getName()}::\${$property->getName()}: Interface cannot have initialized properties."); + } elseif (!$property->getHooks()) { + throw new Nette\InvalidStateException("Property {$this->getName()}::\${$property->getName()}: Interface cannot have properties without hooks."); + } + } + } + + public function __clone(): void { parent::__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); } } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index ee22c9b4..113665fa 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -198,9 +198,9 @@ public function printClass( } $properties = []; - if ($class instanceof ClassType || $class instanceof TraitType) { + if ($class instanceof ClassType || $class instanceof TraitType || $class instanceof InterfaceType) { foreach ($class->getProperties() as $property) { - $properties[] = $this->printProperty($property, $readOnlyClass); + $properties[] = $this->printProperty($property, $readOnlyClass, $class instanceof InterfaceType); } } @@ -376,7 +376,7 @@ private function printConstant(Constant $const): string } - private function printProperty(Property $property, bool $readOnlyClass = false): string + private function printProperty(Property $property, bool $readOnlyClass = false, bool $isInterface = false): string { $property->validate(); $type = $property->getType(); @@ -395,7 +395,7 @@ private function printProperty(Property $property, bool $readOnlyClass = false): . $this->printAttributes($property->getAttributes()) . $def . $defaultValue - . ($this->printHooks($property) ?: ';') + . ($this->printHooks($property, $isInterface) ?: ';') . "\n"; } @@ -456,27 +456,34 @@ protected function printAttributes(array $attrs, bool $inline = false): string } - private function printHooks(Property|PromotedParameter $property): string + private function printHooks(Property|PromotedParameter $property, bool $isInterface = false): string { $hooks = $property->getHooks(); if (!$hooks) { return ''; } + $simple = true; foreach ($property->getHooks() as $type => $hook) { + $simple = $simple && ($hook->isAbstract() || $isInterface); $hooks[$type] = $this->printDocComment($hook) . $this->printAttributes($hook->getAttributes()) - . ($hook->isFinal() ? 'final ' : '') - . ($hook->getReturnReference() ? '&' : '') - . $type - . ($hook->getParameters() ? $this->printParameters($hook) : '') - . ' ' - . ($hook->isShort() - ? '=> ' . $hook->getBody() . ';' - : "{\n" . $this->indent($this->printFunctionBody($hook)) . '}'); + . ($hook->isAbstract() || $isInterface + ? ($hook->getReturnReference() ? '&' : '') + . $type . ';' + : ($hook->isFinal() ? 'final ' : '') + . ($hook->getReturnReference() ? '&' : '') + . $type + . ($hook->getParameters() ? $this->printParameters($hook) : '') + . ' ' + . ($hook->isShort() + ? '=> ' . $hook->getBody() . ';' + : "{\n" . $this->indent($this->printFunctionBody($hook)) . '}')); } - return " {\n" . $this->indent(implode("\n", $hooks)) . "\n}"; + return $simple + ? ' { ' . implode(' ', $hooks) . ' }' + : " {\n" . $this->indent(implode("\n", $hooks)) . "\n}"; } diff --git a/src/PhpGenerator/PropertyHook.php b/src/PhpGenerator/PropertyHook.php index 3c831fd4..f65e22ac 100644 --- a/src/PhpGenerator/PropertyHook.php +++ b/src/PhpGenerator/PropertyHook.php @@ -18,6 +18,7 @@ final class PropertyHook private string $body = ''; private bool $short = false; private bool $final = false; + private bool $abstract = false; /** @var Parameter[] */ private array $parameters = []; @@ -65,6 +66,19 @@ public function isFinal(): bool } + public function setAbstract(bool $state = true): static + { + $this->abstract = $state; + return $this; + } + + + public function isAbstract(): bool + { + return $this->abstract; + } + + /** @internal */ public function setParameters(array $val): static { diff --git a/tests/PhpGenerator/ClassManipulator.implement.84.phpt b/tests/PhpGenerator/ClassManipulator.implement.84.phpt new file mode 100644 index 00000000..a26d51e0 --- /dev/null +++ b/tests/PhpGenerator/ClassManipulator.implement.84.phpt @@ -0,0 +1,81 @@ +implement(TestInterface::class); +Assert::match(<<<'XX' + class TestClass implements TestInterface + { + public array $interfaceProperty; + + + function interfaceMethod() + { + } + } + + XX, (string) $class); + + +// Test abstract class extension +$class = new ClassType('TestClass'); +$manipulator = new ClassManipulator($class); +$manipulator->implement(TestAbstract::class); +Assert::match(<<<'XX' + class TestClass extends TestAbstract + { + public array $abstractProperty; + + + public function abstractMethod() + { + } + } + + XX, (string) $class); + + +// Test exception for regular class +Assert::exception( + fn() => $manipulator->implement(stdClass::class), + InvalidArgumentException::class, + "'stdClass' is not an interface or abstract class." +); diff --git a/tests/PhpGenerator/ClassManipulator.implement.phpt b/tests/PhpGenerator/ClassManipulator.implement.phpt index 4558d591..e5d85114 100644 --- a/tests/PhpGenerator/ClassManipulator.implement.phpt +++ b/tests/PhpGenerator/ClassManipulator.implement.phpt @@ -20,6 +20,9 @@ interface TestInterface extends ParentInterface abstract class ParentAbstract { + public array $concreteProperty; + + abstract public function abstractMethod(); diff --git a/tests/PhpGenerator/ClassManipulator.inheritProperty.phpt b/tests/PhpGenerator/ClassManipulator.inheritProperty.phpt index d9b9bf1b..95d533aa 100644 --- a/tests/PhpGenerator/ClassManipulator.inheritProperty.phpt +++ b/tests/PhpGenerator/ClassManipulator.inheritProperty.phpt @@ -21,14 +21,14 @@ $manipulator = new ClassManipulator($class); Assert::exception( fn() => $manipulator->inheritProperty('bar'), Nette\InvalidStateException::class, - "Class 'Test' has not setExtends() set.", + "Class 'Test' has neither setExtends() nor setImplements() set.", ); $class->setExtends('Unknown'); Assert::exception( fn() => $manipulator->inheritProperty('bar'), Nette\InvalidStateException::class, - "Property 'bar' has not been found in ancestor Unknown", + "Property 'bar' has not been found in any ancestor: Unknown", ); diff --git a/tests/PhpGenerator/InterfaceType.validate.phpt b/tests/PhpGenerator/InterfaceType.validate.phpt new file mode 100644 index 00000000..b036e425 --- /dev/null +++ b/tests/PhpGenerator/InterfaceType.validate.phpt @@ -0,0 +1,21 @@ +addProperty('first', 123); + $interface->validate(); +}, Nette\InvalidStateException::class, 'Property Demo::$first: Interface cannot have initialized properties.'); + +Assert::exception(function () { + $interface = new InterfaceType('Demo'); + $interface->addProperty('first'); + $interface->validate(); +}, Nette\InvalidStateException::class, 'Property Demo::$first: Interface cannot have properties without hooks.'); diff --git a/tests/PhpGenerator/PropertyLike.hooks.phpt b/tests/PhpGenerator/PropertyLike.hooks.phpt index bfdfbf4e..51e6f56f 100644 --- a/tests/PhpGenerator/PropertyLike.hooks.phpt +++ b/tests/PhpGenerator/PropertyLike.hooks.phpt @@ -7,6 +7,7 @@ declare(strict_types=1); use Nette\PhpGenerator\ClassType; +use Nette\PhpGenerator\InterfaceType; use Nette\PhpGenerator\PropertyHookType; require __DIR__ . '/../bootstrap.php'; @@ -95,3 +96,27 @@ same(<<<'XX' } XX, (string) $class); + + +$interface = new InterfaceType('Demo'); + +$interface->addProperty('first') + ->setType('int') + ->setPublic() + ->addHook('get'); + +$prop = $interface->addProperty('second') + ->setType('Value') + ->setPublic(); + +$prop->addHook('get'); +$prop->addHook('set'); + +same(<<<'XX' + interface Demo + { + public int $first { get; } + public Value $second { set; get; } + } + + XX, (string) $interface); From b9b02a3b8172396e1636d19b81b10ce6166133c7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 27 Nov 2024 18:02:04 +0100 Subject: [PATCH 239/266] Property can be abstract / final --- readme.md | 15 ++++++ src/PhpGenerator/ClassManipulator.php | 2 +- src/PhpGenerator/Printer.php | 6 ++- src/PhpGenerator/Property.php | 37 ++++++++++++++ .../PhpGenerator/Property.abstract-final.phpt | 48 +++++++++++++++++++ tests/PhpGenerator/Property.validate.phpt | 21 ++++++++ 6 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 tests/PhpGenerator/Property.abstract-final.phpt create mode 100644 tests/PhpGenerator/Property.validate.phpt diff --git a/readme.md b/readme.md index 4b34e3b1..b8155370 100644 --- a/readme.md +++ b/readme.md @@ -699,6 +699,21 @@ class Demo } ``` +Properties and property hooks can be abstract or final: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', 'strtolower($value)') + ->setFinal(); +``` + +   Namespace diff --git a/src/PhpGenerator/ClassManipulator.php b/src/PhpGenerator/ClassManipulator.php index 48ca7ef0..86782958 100644 --- a/src/PhpGenerator/ClassManipulator.php +++ b/src/PhpGenerator/ClassManipulator.php @@ -116,7 +116,7 @@ private function implementMethod(\ReflectionMethod $rm): Method private function implementProperty(\ReflectionProperty $rp): Property { $property = (new Factory)->fromPropertyReflection($rp); - $property->setHooks([]); + $property->setHooks([])->setAbstract(false); $this->class->addMember($property); return $property; } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 113665fa..39336d15 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -380,12 +380,14 @@ private function printProperty(Property $property, bool $readOnlyClass = false, { $property->validate(); $type = $property->getType(); - $def = (($property->getVisibility() ?: 'public') + $def = ($property->isAbstract() && !$isInterface ? 'abstract ' : '') + . ($property->isFinal() ? 'final ' : '') + . ($property->getVisibility() ?: 'public') . ($property->isStatic() ? ' static' : '') . (!$readOnlyClass && $property->isReadOnly() && $type ? ' readonly' : '') . ' ' . ltrim($this->printType($type, $property->isNullable()) . ' ') - . '$' . $property->getName()); + . '$' . $property->getName(); $defaultValue = $property->getValue() === null && !$property->isInitialized() ? '' diff --git a/src/PhpGenerator/Property.php b/src/PhpGenerator/Property.php index c627e36a..f8d6c72e 100644 --- a/src/PhpGenerator/Property.php +++ b/src/PhpGenerator/Property.php @@ -28,6 +28,8 @@ final class Property private ?string $type = null; private bool $nullable = false; private bool $initialized = false; + private bool $final = false; + private bool $abstract = false; public function setValue(mixed $val): static @@ -99,11 +101,46 @@ public function isInitialized(): bool } + public function setFinal(bool $state = true): static + { + $this->final = $state; + return $this; + } + + + public function isFinal(): bool + { + return $this->final; + } + + + public function setAbstract(bool $state = true): static + { + $this->abstract = $state; + return $this; + } + + + public function isAbstract(): bool + { + return $this->abstract; + } + + /** @throws Nette\InvalidStateException */ public function validate(): void { if ($this->readOnly && !$this->type) { throw new Nette\InvalidStateException("Property \$$this->name: Read-only properties are only supported on typed property."); + + } elseif ($this->abstract && $this->final) { + throw new Nette\InvalidStateException("Property \$$this->name cannot be abstract and final at the same time."); + + } elseif ( + $this->abstract + && !Nette\Utils\Arrays::some($this->getHooks(), fn($hook) => $hook->isAbstract()) + ) { + throw new Nette\InvalidStateException("Property \$$this->name: Abstract property must have at least one abstract hook."); } } diff --git a/tests/PhpGenerator/Property.abstract-final.phpt b/tests/PhpGenerator/Property.abstract-final.phpt new file mode 100644 index 00000000..11738a80 --- /dev/null +++ b/tests/PhpGenerator/Property.abstract-final.phpt @@ -0,0 +1,48 @@ +setAbstract(); + +$class->addProperty('first') + ->setType('string') + ->setAbstract() + ->addHook('set') + ->setAbstract(); + +$prop = $class->addProperty('second') + ->setType('string') + ->setAbstract(); + +$prop->addHook('set') + ->setAbstract(); + +$prop->addHook('get', '123'); + +$class->addProperty('third') + ->setFinal(); + +same(<<<'XX' + abstract class Demo + { + abstract public string $first { set; } + + abstract public string $second { + set; + get => 123; + } + + final public $third; + } + + XX, (string) $class); diff --git a/tests/PhpGenerator/Property.validate.phpt b/tests/PhpGenerator/Property.validate.phpt new file mode 100644 index 00000000..e12a4125 --- /dev/null +++ b/tests/PhpGenerator/Property.validate.phpt @@ -0,0 +1,21 @@ +setFinal()->setAbstract(); + $property->validate(); +}, Nette\InvalidStateException::class, 'Property $a cannot be abstract and final at the same time.'); + +Assert::exception(function () { + $property = new Property('a'); + $property->setAbstract(); + $property->validate(); +}, Nette\InvalidStateException::class, 'Property $a: Abstract property must have at least one abstract hook.'); From 9fa32362fb2de516691c1113473caf300858efdb Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 27 Nov 2024 17:20:17 +0100 Subject: [PATCH 240/266] added support for asymmetric visibility --- readme.md | 32 ++++++++ src/PhpGenerator/Printer.php | 14 +++- src/PhpGenerator/PropertyAccessMode.php | 33 ++++++++ src/PhpGenerator/Traits/PropertyLike.php | 72 ++++++++++++++++- .../PropertyLike.asymmetric-visiblity.phpt | 80 +++++++++++++++++++ 5 files changed, 227 insertions(+), 4 deletions(-) create mode 100644 src/PhpGenerator/PropertyAccessMode.php create mode 100644 tests/PhpGenerator/PropertyLike.asymmetric-visiblity.phpt diff --git a/readme.md b/readme.md index b8155370..ae6ef327 100644 --- a/readme.md +++ b/readme.md @@ -713,6 +713,38 @@ $class->addProperty('role') ->setFinal(); ``` +  + +Asymmetric Visibility +--------------------- + +PHP 8.4 introduces asymmetric visibility for properties. You can set different access levels for reading and writing. +The visibility can be set using either the `setVisibility()` method with two parameters, or by using `setPublic()`, `setProtected()`, or `setPrivate()` with the `mode` parameter that specifies whether the visibility applies to getting or setting the property. The default mode is 'get'. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('name') + ->setType('string') + ->setVisibility('public', 'private'); // public for read, private for write + +$class->addProperty('id') + ->setType('int') + ->setProtected('set'); // protected for write + +echo $class; +``` + +This generates: + +```php +class Demo +{ + public private(set) string $name; + + protected(set) int $id; +} +```   diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 39336d15..ab3a9aee 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -344,7 +344,7 @@ private function formatParameters(Closure|GlobalFunction|Method|PropertyHook $fu $this->printDocComment($param) . ($attrs ? ($multiline ? substr($attrs, 0, -1) . "\n" : $attrs) : '') . ($param instanceof PromotedParameter - ? ($param->getVisibility() ?: 'public') . ($param->isReadOnly() && $param->getType() ? ' readonly' : '') . ' ' + ? $this->printPropertyVisibility($param) . ($param->isReadOnly() && $param->getType() ? ' readonly' : '') . ' ' : '') . ltrim($this->printType($param->getType(), $param->isNullable()) . ' ') . ($param->isReference() ? '&' : '') @@ -382,7 +382,7 @@ private function printProperty(Property $property, bool $readOnlyClass = false, $type = $property->getType(); $def = ($property->isAbstract() && !$isInterface ? 'abstract ' : '') . ($property->isFinal() ? 'final ' : '') - . ($property->getVisibility() ?: 'public') + . $this->printPropertyVisibility($property) . ($property->isStatic() ? ' static' : '') . (!$readOnlyClass && $property->isReadOnly() && $type ? ' readonly' : '') . ' ' @@ -402,6 +402,16 @@ private function printProperty(Property $property, bool $readOnlyClass = false, } + private function printPropertyVisibility(Property|PromotedParameter $param): string + { + $get = $param->getVisibility(PropertyAccessMode::Get); + $set = $param->getVisibility(PropertyAccessMode::Set); + return $set + ? ($get ? "$get $set(set)" : "$set(set)") + : $get ?? 'public'; + } + + protected function printType(?string $type, bool $nullable): string { if ($type === null) { diff --git a/src/PhpGenerator/PropertyAccessMode.php b/src/PhpGenerator/PropertyAccessMode.php new file mode 100644 index 00000000..ac53aeb8 --- /dev/null +++ b/src/PhpGenerator/PropertyAccessMode.php @@ -0,0 +1,33 @@ + ?string, 'get' => ?string} */ + private array $visibility = [PropertyAccessMode::Set => null, PropertyAccessMode::Get => null]; private bool $readOnly = false; /** @var array */ private array $hooks = [PropertyHookType::Set => null, PropertyHookType::Get => null]; + /** + * @param 'public'|'protected'|'private'|null $get + * @param 'public'|'protected'|'private'|null $set + */ + public function setVisibility(?string $get, ?string $set = null): static + { + $this->visibility = [ + PropertyAccessMode::Set => $set === null ? $set : Visibility::from($set), + PropertyAccessMode::Get => $get === null ? $get : Visibility::from($get), + ]; + return $this; + } + + + /** @param 'set'|'get' $mode */ + public function getVisibility(string $mode = PropertyAccessMode::Get): ?string + { + return $this->visibility[PropertyAccessMode::from($mode)]; + } + + + /** @param 'set'|'get' $mode */ + public function setPublic(string $mode = PropertyAccessMode::Get): static + { + $this->visibility[PropertyAccessMode::from($mode)] = Visibility::Public; + return $this; + } + + + /** @param 'set'|'get' $mode */ + public function isPublic(string $mode = PropertyAccessMode::Get): bool + { + return in_array($this->visibility[PropertyAccessMode::from($mode)], [Visibility::Public, null], true); + } + + + /** @param 'set'|'get' $mode */ + public function setProtected(string $mode = PropertyAccessMode::Get): static + { + $this->visibility[PropertyAccessMode::from($mode)] = Visibility::Protected; + return $this; + } + + + /** @param 'set'|'get' $mode */ + public function isProtected(string $mode = PropertyAccessMode::Get): bool + { + return $this->visibility[PropertyAccessMode::from($mode)] === Visibility::Protected; + } + + + /** @param 'set'|'get' $mode */ + public function setPrivate(string $mode = PropertyAccessMode::Get): static + { + $this->visibility[PropertyAccessMode::from($mode)] = Visibility::Private; + return $this; + } + + + /** @param 'set'|'get' $mode */ + public function isPrivate(string $mode = PropertyAccessMode::Get): bool + { + return $this->visibility[PropertyAccessMode::from($mode)] === Visibility::Private; + } + + public function setReadOnly(bool $state = true): static { $this->readOnly = $state; diff --git a/tests/PhpGenerator/PropertyLike.asymmetric-visiblity.phpt b/tests/PhpGenerator/PropertyLike.asymmetric-visiblity.phpt new file mode 100644 index 00000000..bc32b071 --- /dev/null +++ b/tests/PhpGenerator/PropertyLike.asymmetric-visiblity.phpt @@ -0,0 +1,80 @@ +addProperty('first') + ->setType('string'); +Assert::true($default->isPublic(PropertyAccessMode::Get)); +Assert::true($default->isPublic(PropertyAccessMode::Set)); +Assert::null($default->getVisibility()); +Assert::null($default->getVisibility('set')); + +// Public with private setter +$restricted = $class->addProperty('second') + ->setType('string') + ->setVisibility(null, 'private'); +Assert::true($restricted->isPublic()); +Assert::false($restricted->isPublic('set')); +Assert::true($restricted->isPrivate('set')); +Assert::null($restricted->getVisibility()); +Assert::same('private', $restricted->getVisibility('set')); + +// Public with protected setter using individual methods +$mixed = $class->addProperty('third') + ->setType('string') + ->setPublic() + ->setProtected('set'); +Assert::true($mixed->isPublic()); +Assert::false($mixed->isPublic('set')); +Assert::true($mixed->isProtected('set')); +Assert::same('public', $mixed->getVisibility()); +Assert::same('protected', $mixed->getVisibility('set')); + +// Protected with private setter +$nested = $class->addProperty('fourth') + ->setType('string') + ->setProtected() + ->setPrivate('set'); +Assert::false($nested->isPublic()); +Assert::true($nested->isProtected()); +Assert::true($nested->isPrivate('set')); +Assert::same('protected', $nested->getVisibility()); +Assert::same('private', $nested->getVisibility('set')); + +// Test invalid getter visibility +Assert::exception( + fn() => $default->setVisibility('invalid', 'public'), + ValueError::class, +); + +// Test invalid setter visibility +Assert::exception( + fn() => $default->setVisibility('public', 'invalid'), + ValueError::class, +); + + +same(<<<'XX' + class Demo + { + public string $first; + private(set) string $second; + public protected(set) string $third; + protected private(set) string $fourth; + } + + XX, (string) $class); From d201c9bc217e0969d1b678d286be49302972fb56 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 25 Nov 2024 06:30:15 +0100 Subject: [PATCH 241/266] Factory & Extractor: added support for property hooks & asymmetric visibility --- src/PhpGenerator/Extractor.php | 66 +++++-- src/PhpGenerator/Factory.php | 46 ++++- tests/PhpGenerator/ClassType.from.84.phpt | 22 +++ tests/PhpGenerator/Extractor.extractAll.phpt | 5 + .../expected/ClassType.from.84.expect | 163 +++++++++++++++++ .../expected/Extractor.classes.84.expect | 162 +++++++++++++++++ tests/PhpGenerator/fixtures/classes.84.php | 170 ++++++++++++++++++ 7 files changed, 623 insertions(+), 11 deletions(-) create mode 100644 tests/PhpGenerator/ClassType.from.84.phpt create mode 100644 tests/PhpGenerator/expected/ClassType.from.84.expect create mode 100644 tests/PhpGenerator/expected/Extractor.classes.84.expect create mode 100644 tests/PhpGenerator/fixtures/classes.84.php diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 68127f49..97d2f732 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -11,6 +11,7 @@ use Nette; use PhpParser; +use PhpParser\Modifiers; use PhpParser\Node; use PhpParser\NodeFinder; use PhpParser\ParserFactory; @@ -323,7 +324,7 @@ private function addPropertyToClass(ClassLike $class, Node\Stmt\Property $node): foreach ($node->props as $item) { $prop = $class->addProperty($item->name->toString()); $prop->setStatic($node->isStatic()); - $prop->setVisibility($this->toVisibility($node->flags)); + $prop->setVisibility($this->toVisibility($node->flags), $this->toSetterVisibility($node->flags)); $prop->setType($node->type ? $this->toPhp($node->type) : null); if ($item->default) { $prop->setValue($this->toValue($item->default)); @@ -331,6 +332,29 @@ private function addPropertyToClass(ClassLike $class, Node\Stmt\Property $node): $prop->setReadOnly((method_exists($node, 'isReadonly') && $node->isReadonly()) || ($class instanceof ClassType && $class->isReadOnly())); $this->addCommentAndAttributes($prop, $node); + + $prop->setAbstract((bool) ($node->flags & Node\Stmt\Class_::MODIFIER_ABSTRACT)); + $prop->setFinal((bool) ($node->flags & Node\Stmt\Class_::MODIFIER_FINAL)); + $this->addHooksToProperty($prop, $node); + } + } + + + private function addHooksToProperty(Property|PromotedParameter $prop, Node\Stmt\Property|Node\Param $node): void + { + if (!class_exists(Node\PropertyHook::class)) { + return; + } + + foreach ($node->hooks as $hookNode) { + $hook = $prop->addHook($hookNode->name->toString()); + $hook->setFinal((bool) ($hookNode->flags & Modifiers::FINAL)); + $this->setupFunction($hook, $hookNode); + if ($hookNode->body === null) { + $hook->setAbstract(); + } elseif (!is_array($hookNode->body)) { + $hook->setBody($this->getReformattedContents([$hookNode->body], 1), short: true); + } } } @@ -380,7 +404,7 @@ private function addFunctionToFile(PhpFile $phpFile, Node\Stmt\Function_ $node): private function addCommentAndAttributes( - PhpFile|ClassLike|Constant|Property|GlobalFunction|Method|Parameter|EnumCase|TraitUse $element, + PhpFile|ClassLike|Constant|Property|GlobalFunction|Method|Parameter|EnumCase|TraitUse|PropertyHook $element, Node $node, ): void { @@ -408,19 +432,29 @@ private function addCommentAndAttributes( } - private function setupFunction(GlobalFunction|Method $function, Node\FunctionLike $node): void + private function setupFunction(GlobalFunction|Method|PropertyHook $function, Node\FunctionLike $node): void { $function->setReturnReference($node->returnsByRef()); - $function->setReturnType($node->getReturnType() ? $this->toPhp($node->getReturnType()) : null); + if (!$function instanceof PropertyHook) { + $function->setReturnType($node->getReturnType() ? $this->toPhp($node->getReturnType()) : null); + } + foreach ($node->getParams() as $item) { - $visibility = $this->toVisibility($item->flags); - $isReadonly = (bool) ($item->flags & Node\Stmt\Class_::MODIFIER_READONLY); - $param = $visibility - ? ($function->addPromotedParameter($item->var->name))->setVisibility($visibility)->setReadonly($isReadonly) - : $function->addParameter($item->var->name); + $getVisibility = $this->toVisibility($item->flags); + $setVisibility = $this->toSetterVisibility($item->flags); + if ($getVisibility || $setVisibility) { + $param = $function->addPromotedParameter($item->var->name) + ->setVisibility($getVisibility, $setVisibility) + ->setReadonly((bool) ($item->flags & Node\Stmt\Class_::MODIFIER_READONLY)); + $this->addHooksToProperty($param, $item); + } else { + $param = $function->addParameter($item->var->name); + } $param->setType($item->type ? $this->toPhp($item->type) : null); $param->setReference($item->byRef); - $function->setVariadic($item->variadic); + if (!$function instanceof PropertyHook) { + $function->setVariadic($item->variadic); + } if ($item->default) { $param->setDefaultValue($this->toValue($item->default)); } @@ -491,6 +525,18 @@ private function toVisibility(int $flags): ?string } + private function toSetterVisibility(int $flags): ?string + { + return match (true) { + !class_exists(Node\PropertyHook::class) => null, + (bool) ($flags & Modifiers::PUBLIC_SET) => Visibility::Public, + (bool) ($flags & Modifiers::PROTECTED_SET) => Visibility::Protected, + (bool) ($flags & Modifiers::PRIVATE_SET) => Visibility::Private, + default => null, + }; + } + + private function toPhp(Node $value): string { $dolly = clone $value; diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 22a45f7b..12a5687d 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -204,6 +204,7 @@ public function fromParameterReflection(\ReflectionParameter $from): Parameter $param = (new PromotedParameter($from->name)) ->setVisibility($this->getVisibility($property)) ->setReadOnly(PHP_VERSION_ID >= 80100 && $property->isReadonly()); + $this->addHooks($property, $param); } else { $param = new Parameter($from->name); } @@ -260,15 +261,58 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property $prop->setStatic($from->isStatic()); $prop->setVisibility($this->getVisibility($from)); $prop->setType((string) $from->getType()); - $prop->setInitialized($from->hasType() && array_key_exists($prop->getName(), $defaults)); $prop->setReadOnly(PHP_VERSION_ID >= 80100 && $from->isReadOnly()); $prop->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); $prop->setAttributes($this->getAttributes($from)); + + if (PHP_VERSION_ID >= 80400) { + $this->addHooks($from, $prop); + $isInterface = $from->getDeclaringClass()->isInterface(); + $prop->setFinal($from->isFinal() && !$prop->isPrivate(PropertyAccessMode::Set)); + $prop->setAbstract($from->isAbstract() && !$isInterface); + } return $prop; } + private function addHooks(\ReflectionProperty $from, Property|PromotedParameter $prop): void + { + if (PHP_VERSION_ID < 80400) { + return; + } + + $getV = $this->getVisibility($from); + $setV = $from->isPrivateSet() + ? Visibility::Private + : ($from->isProtectedSet() ? Visibility::Protected : $getV); + $defaultSetV = $from->isReadOnly() && $getV !== Visibility::Private + ? Visibility::Protected + : $getV; + if ($setV !== $defaultSetV) { + $prop->setVisibility($getV === Visibility::Public ? null : $getV, $setV); + } + + foreach ($from->getHooks() as $type => $hook) { + $params = $hook->getParameters(); + if ( + count($params) === 1 + && $params[0]->getName() === 'value' + && $params[0]->getType() == $from->getType() // intentionally == + ) { + $params = []; + } + $prop->addHook($type) + ->setParameters(array_map([$this, 'fromParameterReflection'], $params)) + ->setAbstract($hook->isAbstract()) + ->setFinal($hook->isFinal()) + ->setReturnReference($hook->returnsReference()) + ->setComment(Helpers::unformatDocComment((string) $hook->getDocComment())) + ->setAttributes($this->getAttributes($hook)); + } + } + + public function fromObject(object $obj): Literal { return new Literal('new \\' . $obj::class . '(/* unknown */)'); diff --git a/tests/PhpGenerator/ClassType.from.84.phpt b/tests/PhpGenerator/ClassType.from.84.phpt new file mode 100644 index 00000000..984d9a85 --- /dev/null +++ b/tests/PhpGenerator/ClassType.from.84.phpt @@ -0,0 +1,22 @@ +extractAll(); sameFile(__DIR__ . '/expected/Extractor.classes.82.expect', (string) $file); +if (class_exists(PhpParser\Node\PropertyHook::class)) { + $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.84.php')))->extractAll(); + sameFile(__DIR__ . '/expected/Extractor.classes.84.expect', (string) $file); +} + $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/enum.php')))->extractAll(); sameFile(__DIR__ . '/expected/Extractor.enum.expect', (string) $file); diff --git a/tests/PhpGenerator/expected/ClassType.from.84.expect b/tests/PhpGenerator/expected/ClassType.from.84.expect new file mode 100644 index 00000000..f2ef803b --- /dev/null +++ b/tests/PhpGenerator/expected/ClassType.from.84.expect @@ -0,0 +1,163 @@ +class PropertyHookSignatures +{ + public string $basic { + get { + } + } + + public string $fullGet { + get { + } + } + + protected string $refGet { + &get { + } + } + + protected string $finalGet { + final get { + } + } + + public string $basicSet { + set { + } + } + + public string $fullSet { + set { + } + } + + public string $setWithParam { + set(string $foo) { + } + } + + public string $setWithParam2 { + set(string|int $value) { + } + } + + public string $finalSet { + final set { + } + } + + public string $combined { + set { + } + get { + } + } + + final public string $combinedFinal { + /** comment set */ + #[Set] + set { + } + /** comment get */ + #[Get] + get { + } + } + + public string $virtualProp { + set { + } + &get { + } + } +} + +abstract class AbstractHookSignatures +{ + abstract public string $abstractGet { get; } + abstract protected string $abstractSet { set; } + abstract public string $abstractBoth { set; get; } + + abstract public string $mixedGet { + set { + } + get; + } + + abstract public string $mixedSet { + set; + get { + } + } +} + +interface InterfaceHookSignatures +{ + public string $get { get; } + + public string $set { #[Set] + set; } + public string $both { set; get; } + public string $refGet { &get; } +} + +class AsymmetricVisibilitySignatures +{ + private(set) string $first; + protected(set) string $second; + protected private(set) string $third; + private(set) string $fourth; + protected(set) string $fifth; + public readonly string $implicit; + private(set) readonly string $readFirst; + private(set) readonly string $readSecond; + protected readonly string $readThird; + public(set) readonly string $readFourth; + private(set) string $firstFinal; + final protected(set) string $secondFinal; + protected private(set) string $thirdFinal; + private(set) string $fourthFinal; + final protected(set) string $fifthFinal; +} + +class CombinedSignatures +{ + protected(set) string $prop2 { + final set { + } + get { + } + } + + protected private(set) string $prop3 { + set { + } + final get { + } + } +} + +class ConstructorAllSignatures +{ + public function __construct( + private(set) string $prop1, + protected(set) string $prop2, + protected private(set) string $prop3, + private(set) string $prop4, + protected(set) string $prop5, + private(set) readonly string $readProp1, + private(set) readonly string $readProp2, + protected readonly string $readProp3, + public(set) readonly string $readProp4, + public string $hookProp1 { + get { + } + }, + protected(set) string $mixedProp1 { + set { + } + get { + } + }, + ) { + } +} diff --git a/tests/PhpGenerator/expected/Extractor.classes.84.expect b/tests/PhpGenerator/expected/Extractor.classes.84.expect new file mode 100644 index 00000000..ba6cfc57 --- /dev/null +++ b/tests/PhpGenerator/expected/Extractor.classes.84.expect @@ -0,0 +1,162 @@ + 'x'; + } + + public string $fullGet { + get { + return 'x'; + } + } + + protected string $refGet { + &get { + return 'x'; + } + } + + protected string $finalGet { + final get => 'x'; + } + + public string $basicSet { + set => 'x'; + } + + public string $fullSet { + set { + } + } + + public string $setWithParam { + set(string $foo) { + } + } + + public string $setWithParam2 { + set(string|int $value) => ''; + } + + public string $finalSet { + final set { + } + } + + public string $combined { + set(string $value) { + } + get => 'x'; + } + + final public string $combinedFinal { + /** comment set */ + #[Set] + set { + } + /** comment get */ + #[Get] + get => 'x'; + } + + public string $virtualProp { + set { + } + &get => 'x'; + } +} + +abstract class AbstractHookSignatures +{ + abstract public string $abstractGet { get; } + abstract protected string $abstractSet { set; } + abstract public string $abstractBoth { set; get; } + + abstract public string $mixedGet { + set => 'x'; + get; + } + + abstract public string $mixedSet { + set; + get => 'x'; + } +} + +interface InterfaceHookSignatures +{ + public string $get { get; } + + public string $set { #[Set] + set; } + public string $both { set; get; } + public string $refGet { &get; } +} + +class AsymmetricVisibilitySignatures +{ + public private(set) string $first; + public protected(set) string $second; + protected private(set) string $third; + private(set) string $fourth; + protected(set) string $fifth; + public readonly string $implicit; + public private(set) readonly string $readFirst; + private(set) readonly string $readSecond; + protected protected(set) readonly string $readThird; + public public(set) readonly string $readFourth; + final public private(set) string $firstFinal; + final public protected(set) string $secondFinal; + final protected private(set) string $thirdFinal; + final private(set) string $fourthFinal; + final protected(set) string $fifthFinal; +} + +class CombinedSignatures +{ + public protected(set) string $prop2 { + final set { + } + get { + return 'x'; + } + } + + protected private(set) string $prop3 { + set(string $value) { + } + final get => 'x'; + } +} + +class ConstructorAllSignatures +{ + public function __construct( + public private(set) string $prop1, + public protected(set) string $prop2, + protected private(set) string $prop3, + private(set) string $prop4, + protected(set) string $prop5, + public private(set) readonly string $readProp1, + private(set) readonly string $readProp2, + protected protected(set) readonly string $readProp3, + public public(set) readonly string $readProp4, + public string $hookProp1 { + get => 'x'; + }, + public protected(set) string $mixedProp1 { + set { + } + get { + return 'x'; + } + }, + ) { + } +} diff --git a/tests/PhpGenerator/fixtures/classes.84.php b/tests/PhpGenerator/fixtures/classes.84.php new file mode 100644 index 00000000..9b2367e5 --- /dev/null +++ b/tests/PhpGenerator/fixtures/classes.84.php @@ -0,0 +1,170 @@ + 'x'; + } + + public string $fullGet { + get { return 'x'; } + } + + protected string $refGet { + &get { return 'x'; } + } + + protected string $finalGet { + final get => 'x'; + } + + // Set variants + public string $basicSet { + set => 'x'; + } + + public string $fullSet { + set { } + } + + public string $setWithParam { + set(string $foo) { } + } + + public string $setWithParam2 { + set(string|int $value) => ''; + } + + public string $finalSet { + final set { } + } + + // Combinations + public string $combined { + set(string $value) { } + get => 'x'; + } + + final public string $combinedFinal { + /** comment set */ + #[Set] + set { } + /** comment get */ + #[Get] + get => 'x'; + } + + public string $virtualProp { + set { } + &get => 'x'; + } +} + +// Abstract hooks +abstract class AbstractHookSignatures +{ + // Abstract variants + abstract public string $abstractGet { get; } + + abstract protected string $abstractSet { set; } + + abstract public string $abstractBoth { set; get; } + // Combination of abstract/concrete + abstract public string $mixedGet { + set => 'x'; + get; + } + + abstract public string $mixedSet { + set; + get => 'x'; + } +} + +// Interface with hooks +interface InterfaceHookSignatures +{ + public string $get { get; } + + public string $set { #[Set] set; } + + public string $both { set; get; } + + // Get can be forced as reference + public string $refGet { &get; } +} + +// Asymmetric visibility - all valid combinations +class AsymmetricVisibilitySignatures +{ + // Basic variants + public private(set) string $first; + public protected(set) string $second; + protected private(set) string $third; + private(set) string $fourth; + protected(set) string $fifth; + + // With readonly + public readonly string $implicit; + public private(set) readonly string $readFirst; + private(set) readonly string $readSecond; + protected protected(set) readonly string $readThird; + public public(set) readonly string $readFourth; + + // With final + final public private(set) string $firstFinal; + final public protected(set) string $secondFinal; + final protected private(set) string $thirdFinal; + final private(set) string $fourthFinal; + final protected(set) string $fifthFinal; +} + +// Combination of hooks and asymmetric visibility +class CombinedSignatures +{ + public protected(set) string $prop2 { + final set { } + get { return 'x'; } + } + + protected private(set) string $prop3 { + set(string $value) { } + final get => 'x'; + } +} + +// Constructor property promotion with asymmetric visibility +class ConstructorAllSignatures +{ + public function __construct( + // Basic asymmetric visibility + public private(set) string $prop1, + public protected(set) string $prop2, + protected private(set) string $prop3, + private(set) string $prop4, + protected(set) string $prop5, + + // With readonly + public private(set) readonly string $readProp1, + private(set) readonly string $readProp2, + protected protected(set) readonly string $readProp3, + public public(set) readonly string $readProp4, + + // With hooks + public string $hookProp1 { + get => 'x'; + }, + + // Combination of hooks and asymmetric visibility + public protected(set) string $mixedProp1 { + set { } + get { return 'x'; } + }, + ) {} +} From 2fa32f1b5f5233426b92ac9d28f4e1d41ebfc632 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 16 Jan 2025 05:52:00 +0100 Subject: [PATCH 242/266] cs --- ncs.php | 3 +-- src/PhpGenerator/Dumper.php | 4 ++-- src/PhpGenerator/Helpers.php | 4 ++-- src/PhpGenerator/PhpNamespace.php | 2 +- tests/PhpGenerator/Dumper.dump().phpt | 4 ++-- tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt | 6 +++--- tests/PhpGenerator/PhpFile.phpt | 4 ++-- tests/PhpGenerator/fixtures/classes.82.php | 2 +- 8 files changed, 14 insertions(+), 15 deletions(-) diff --git a/ncs.php b/ncs.php index 6dca0c3d..9c743bdd 100644 --- a/ncs.php +++ b/ncs.php @@ -8,7 +8,6 @@ declare(strict_types=1); return [ - // constant NULL, FALSE in src/PhpGenerator/Type.php - 'constant_case' => false, + // constants in src/PhpGenerator/Type.php 'lowercase_static_reference' => false, ]; diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index b6047067..a3aeafc7 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -73,14 +73,14 @@ private function dumpString(string $s): string $utf8 = preg_match('##u', $s); $escaped = preg_replace_callback( - $utf8 ? '#[\p{C}\\\\]#u' : '#[\x00-\x1F\x7F-\xFF\\\\]#', + $utf8 ? '#[\p{C}\\\]#u' : '#[\x00-\x1F\x7F-\xFF\\\]#', fn($m) => $special[$m[0]] ?? (strlen($m[0]) === 1 ? '\x' . str_pad(strtoupper(dechex(ord($m[0]))), 2, '0', STR_PAD_LEFT) : '\u{' . strtoupper(ltrim(dechex(self::utf8Ord($m[0])), '0')) . '}'), $s, ); return $s === str_replace('\\\\', '\\', $escaped) - ? "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|$)#D', '\\\\$0', $s) . "'" + ? "'" . preg_replace('#\'|\\\(?=[\'\\\]|$)#D', '\\\$0', $s) . "'" : '"' . addcslashes($escaped, '"$') . '"'; } diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 7290d390..fe296991 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -75,7 +75,7 @@ public static function tagName(string $name, string $of = PhpNamespace::NameNorm public static function simplifyTaggedNames(string $code, ?PhpNamespace $namespace): string { - return preg_replace_callback('~/\*\(([ncf])\*/([\w\x7f-\xff\\\\]++)~', function ($m) use ($namespace) { + return preg_replace_callback('~/\*\(([ncf])\*/([\w\x7f-\xff\\\]++)~', function ($m) use ($namespace) { [, $of, $name] = $m; return $namespace ? $namespace->simplifyType($name, $of) @@ -106,7 +106,7 @@ public static function isIdentifier(mixed $value): bool public static function isNamespaceIdentifier(mixed $value, bool $allowLeadingSlash = false): bool { - $re = '#^' . ($allowLeadingSlash ? '\\\\?' : '') . self::ReIdentifier . '(\\\\' . self::ReIdentifier . ')*$#D'; + $re = '#^' . ($allowLeadingSlash ? '\\\?' : '') . self::ReIdentifier . '(\\\\' . self::ReIdentifier . ')*$#D'; return is_string($value) && preg_match($re, $value); } diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index bcd50840..e4d2df62 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -200,7 +200,7 @@ public function resolveName(string $name, string $of = self::NameNormal): string */ public function simplifyType(string $type, string $of = self::NameNormal): string { - return preg_replace_callback('~[\w\x7f-\xff\\\\]+~', fn($m) => $this->simplifyName($m[0], $of), $type); + return preg_replace_callback('~[\w\x7f-\xff\\\]+~', fn($m) => $this->simplifyName($m[0], $of), $type); } diff --git a/tests/PhpGenerator/Dumper.dump().phpt b/tests/PhpGenerator/Dumper.dump().phpt index 379acbf6..d9469a6e 100644 --- a/tests/PhpGenerator/Dumper.dump().phpt +++ b/tests/PhpGenerator/Dumper.dump().phpt @@ -34,8 +34,8 @@ Assert::same("'Hello'", $dumper->dump('Hello')); Assert::same('"\t\n\r\e"', $dumper->dump("\t\n\r\e")); Assert::same('"\u{FEFF}"', $dumper->dump("\xEF\xBB\xBF")); // BOM Assert::same('\'$"\\\\\'', $dumper->dump('$"\\')); -Assert::same('\'$"\\ \x00\'', $dumper->dump('$"\\ \x00')); // no escape -Assert::same('"\\$\\"\\\\ \x00"', $dumper->dump("$\"\\ \x00")); +Assert::same('\'$"\ \x00\'', $dumper->dump('$"\ \x00')); // no escape +Assert::same('"\$\"\\\ \x00"', $dumper->dump("$\"\\ \x00")); Assert::same( "'I\u{F1}t\u{EB}rn\u{E2}ti\u{F4}n\u{E0}liz\u{E6}ti\u{F8}n'", $dumper->dump("I\u{F1}t\u{EB}rn\u{E2}ti\u{F4}n\u{E0}liz\u{E6}ti\u{F8}n"), // Iñtërnâtiônàlizætiøn diff --git a/tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt b/tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt index acdb92d6..17704c22 100644 --- a/tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt +++ b/tests/PhpGenerator/Helpers.isNamespaceIdentifier.phpt @@ -13,9 +13,9 @@ Assert::true(Helpers::isNamespaceIdentifier("\x7F")); Assert::true(Helpers::isNamespaceIdentifier("\x7F\\\x7F")); Assert::false(Helpers::isNamespaceIdentifier('0Item')); Assert::true(Helpers::isNamespaceIdentifier('Item\Item')); -Assert::false(Helpers::isNamespaceIdentifier('Item\\\\Item')); -Assert::false(Helpers::isNamespaceIdentifier('\\Item')); +Assert::false(Helpers::isNamespaceIdentifier('Item\\\Item')); +Assert::false(Helpers::isNamespaceIdentifier('\Item')); Assert::false(Helpers::isNamespaceIdentifier('Item\\')); -Assert::true(Helpers::isNamespaceIdentifier('\\Item', allowLeadingSlash: true)); +Assert::true(Helpers::isNamespaceIdentifier('\Item', allowLeadingSlash: true)); Assert::false(Helpers::isNamespaceIdentifier('Item\\', allowLeadingSlash: true)); diff --git a/tests/PhpGenerator/PhpFile.phpt b/tests/PhpGenerator/PhpFile.phpt index 04a024d9..f50491c0 100644 --- a/tests/PhpGenerator/PhpFile.phpt +++ b/tests/PhpGenerator/PhpFile.phpt @@ -74,7 +74,7 @@ $interfaceF $traitG = $file->addTrait('Baz\G'); Assert::same($file->addNamespace('Baz'), $traitG->getNamespace()); -$file->addFunction('Baz\\f2') +$file->addFunction('Baz\f2') ->setReturnType('Foo\B'); @@ -112,7 +112,7 @@ Assert::same([ 'FooBar\I', ], array_keys($file->getClasses())); -Assert::same(['Baz\\f2', 'f1'], array_keys($file->getFunctions())); +Assert::same(['Baz\f2', 'f1'], array_keys($file->getFunctions())); diff --git a/tests/PhpGenerator/fixtures/classes.82.php b/tests/PhpGenerator/fixtures/classes.82.php index 5ad295b7..c4a70d13 100644 --- a/tests/PhpGenerator/fixtures/classes.82.php +++ b/tests/PhpGenerator/fixtures/classes.82.php @@ -22,5 +22,5 @@ public function func(C|(X&D)|null $foo): (A&B)|null trait Trait13 { - public const FOO = 123; + public const FOO = 123; } From a98347933759648ebcbcb6fdff5b08b82bebace3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 3 Feb 2025 03:31:50 +0100 Subject: [PATCH 243/266] github actions updated --- .github/workflows/coding-style.yml | 4 ++-- .github/workflows/tests.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index 97e6c166..9e412ba6 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: 8.3 coverage: none - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: 8.3 coverage: none - run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ebeec0ac..e4e361f3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,9 +22,9 @@ jobs: - run: composer install --no-progress --prefer-dist - run: vendor/bin/tester tests -s -C - if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: output + name: output-${{ matrix.php }} path: tests/**/output From 7e61e810c7dca1c25f6a5c02eb43aba80c543b38 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 17 Feb 2025 16:20:38 +0100 Subject: [PATCH 244/266] improved tests --- tests/PhpGenerator/ClassType.from.84.phpt | 1 + ...etFunctionBody.phpt => Extractor.extractFunctionBody.phpt} | 0 ...etMethodBodies.phpt => Extractor.extractMethodBodies.phpt} | 0 tests/PhpGenerator/expected/ClassType.from.84.expect | 4 ++++ tests/PhpGenerator/expected/Extractor.classes.84.expect | 4 ++++ tests/PhpGenerator/fixtures/classes.84.php | 4 ++++ 6 files changed, 13 insertions(+) rename tests/PhpGenerator/{Extractor.getFunctionBody.phpt => Extractor.extractFunctionBody.phpt} (100%) rename tests/PhpGenerator/{Extractor.getMethodBodies.phpt => Extractor.extractMethodBodies.phpt} (100%) diff --git a/tests/PhpGenerator/ClassType.from.84.phpt b/tests/PhpGenerator/ClassType.from.84.phpt index 984d9a85..8ad4d562 100644 --- a/tests/PhpGenerator/ClassType.from.84.phpt +++ b/tests/PhpGenerator/ClassType.from.84.phpt @@ -18,5 +18,6 @@ $res[] = InterfaceType::from(Abc\InterfaceHookSignatures::class); $res[] = ClassType::from(Abc\AsymmetricVisibilitySignatures::class); $res[] = ClassType::from(Abc\CombinedSignatures::class); $res[] = ClassType::from(Abc\ConstructorAllSignatures::class); +$res[] = ClassType::from(Abc\PropertyHookSignaturesChild::class); sameFile(__DIR__ . '/expected/ClassType.from.84.expect', implode("\n", $res)); diff --git a/tests/PhpGenerator/Extractor.getFunctionBody.phpt b/tests/PhpGenerator/Extractor.extractFunctionBody.phpt similarity index 100% rename from tests/PhpGenerator/Extractor.getFunctionBody.phpt rename to tests/PhpGenerator/Extractor.extractFunctionBody.phpt diff --git a/tests/PhpGenerator/Extractor.getMethodBodies.phpt b/tests/PhpGenerator/Extractor.extractMethodBodies.phpt similarity index 100% rename from tests/PhpGenerator/Extractor.getMethodBodies.phpt rename to tests/PhpGenerator/Extractor.extractMethodBodies.phpt diff --git a/tests/PhpGenerator/expected/ClassType.from.84.expect b/tests/PhpGenerator/expected/ClassType.from.84.expect index f2ef803b..e7e8e2a0 100644 --- a/tests/PhpGenerator/expected/ClassType.from.84.expect +++ b/tests/PhpGenerator/expected/ClassType.from.84.expect @@ -161,3 +161,7 @@ class ConstructorAllSignatures ) { } } + +class PropertyHookSignaturesChild extends PropertyHookSignatures +{ +} diff --git a/tests/PhpGenerator/expected/Extractor.classes.84.expect b/tests/PhpGenerator/expected/Extractor.classes.84.expect index ba6cfc57..60f5c706 100644 --- a/tests/PhpGenerator/expected/Extractor.classes.84.expect +++ b/tests/PhpGenerator/expected/Extractor.classes.84.expect @@ -160,3 +160,7 @@ class ConstructorAllSignatures ) { } } + +class PropertyHookSignaturesChild extends PropertyHookSignatures +{ +} diff --git a/tests/PhpGenerator/fixtures/classes.84.php b/tests/PhpGenerator/fixtures/classes.84.php index 9b2367e5..ed62f26a 100644 --- a/tests/PhpGenerator/fixtures/classes.84.php +++ b/tests/PhpGenerator/fixtures/classes.84.php @@ -168,3 +168,7 @@ public function __construct( }, ) {} } + +class PropertyHookSignaturesChild extends PropertyHookSignatures +{ +} From ff08574bdd16b5ba581f5aecfb1229d65a4f0d39 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 17 Feb 2025 16:38:15 +0100 Subject: [PATCH 245/266] Factory::fromClassReflection() rejects $withBodies for interfaces --- src/PhpGenerator/Factory.php | 4 ++-- tests/PhpGenerator/ClassType.from.bodies.phpt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 12a5687d..c8125e56 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -31,8 +31,8 @@ public function fromClassReflection( bool $withBodies = false, ): ClassLike { - if ($withBodies && ($from->isAnonymous() || $from->isInternal())) { - throw new Nette\NotSupportedException('The $withBodies parameter cannot be used for anonymous or internal classes.'); + if ($withBodies && ($from->isAnonymous() || $from->isInternal() || $from->isInterface())) { + throw new Nette\NotSupportedException('The $withBodies parameter cannot be used for anonymous or internal classes or interfaces.'); } $enumIface = null; diff --git a/tests/PhpGenerator/ClassType.from.bodies.phpt b/tests/PhpGenerator/ClassType.from.bodies.phpt index d0c667e8..5d0dc9e6 100644 --- a/tests/PhpGenerator/ClassType.from.bodies.phpt +++ b/tests/PhpGenerator/ClassType.from.bodies.phpt @@ -12,7 +12,7 @@ require __DIR__ . '/fixtures/bodies.php'; Assert::exception( fn() => ClassType::from(PDO::class, withBodies: true), Nette\NotSupportedException::class, - 'The $withBodies parameter cannot be used for anonymous or internal classes.', + 'The $withBodies parameter cannot be used for anonymous or internal classes or interfaces.', ); @@ -23,7 +23,7 @@ Assert::exception( } }, withBodies: true), Nette\NotSupportedException::class, - 'The $withBodies parameter cannot be used for anonymous or internal classes.', + 'The $withBodies parameter cannot be used for anonymous or internal classes or interfaces.', ); From 42806049a7774a2bd316c958f5dcf01c6b5c56fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rk=20Magyar?= <14284867+xHeaven@users.noreply.github.com> Date: Mon, 17 Feb 2025 16:15:38 +0100 Subject: [PATCH 246/266] Factory::fromClassReflection() extracts property hook bodies (#172) --- src/PhpGenerator/Extractor.php | 33 +++++++ src/PhpGenerator/Factory.php | 8 +- tests/PhpGenerator/ClassType.from.bodies.phpt | 10 +++ .../Extractor.extractPropertyHookBodies.phpt | 50 +++++++++++ .../expected/ClassType.from.bodies.84.expect | 88 +++++++++++++++++++ 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 tests/PhpGenerator/Extractor.extractPropertyHookBodies.phpt create mode 100644 tests/PhpGenerator/expected/ClassType.from.bodies.84.expect diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 97d2f732..69f43c2f 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -79,6 +79,36 @@ public function extractMethodBodies(string $className): array } + /** @return array> */ + public function extractPropertyHookBodies(string $className): array + { + if (!class_exists(Node\PropertyHook::class)) { + return []; + } + + $nodeFinder = new NodeFinder; + $classNode = $nodeFinder->findFirst( + $this->statements, + fn(Node $node) => $node instanceof Node\Stmt\ClassLike && $node->namespacedName->toString() === $className, + ); + + $res = []; + foreach ($nodeFinder->findInstanceOf($classNode, Node\Stmt\Property::class) as $propertyNode) { + foreach ($propertyNode->props as $propNode) { + $propName = $propNode->name->toString(); + foreach ($propertyNode->hooks as $hookNode) { + $body = $hookNode->body; + if ($body !== null) { + $contents = $this->getReformattedContents(is_array($body) ? $body : [$body], 3); + $res[$propName][$hookNode->name->toString()] = [$contents, !is_array($body)]; + } + } + } + } + return $res; + } + + public function extractFunctionBody(string $name): ?string { $functionNode = (new NodeFinder)->findFirst( @@ -94,6 +124,9 @@ public function extractFunctionBody(string $name): ?string /** @param Node[] $nodes */ private function getReformattedContents(array $nodes, int $level): string { + if (!$nodes) { + return ''; + } $body = $this->getNodeContents(...$nodes); $body = $this->performReplacements($body, $this->prepareReplacements($nodes, $level)); return Helpers::unindent($body, $level); diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index c8125e56..39e5ebab 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -81,7 +81,13 @@ public function fromClassReflection( && !$prop->isPromoted() && !$class->isEnum() ) { - $props[] = $this->fromPropertyReflection($prop); + $props[] = $p = $this->fromPropertyReflection($prop); + if ($withBodies) { + $hookBodies ??= $this->getExtractor($declaringClass->getFileName())->extractPropertyHookBodies($declaringClass->name); + foreach ($hookBodies[$prop->getName()] ?? [] as $hookType => [$body, $short]) { + $p->getHook($hookType)->setBody($body, short: $short); + } + } } } diff --git a/tests/PhpGenerator/ClassType.from.bodies.phpt b/tests/PhpGenerator/ClassType.from.bodies.phpt index 5d0dc9e6..35a99d7d 100644 --- a/tests/PhpGenerator/ClassType.from.bodies.phpt +++ b/tests/PhpGenerator/ClassType.from.bodies.phpt @@ -29,3 +29,13 @@ Assert::exception( $res = ClassType::from(Abc\Class7::class, withBodies: true); sameFile(__DIR__ . '/expected/ClassType.from.bodies.expect', (string) $res); + + +if (PHP_VERSION_ID >= 80400) { + require __DIR__ . '/fixtures/classes.84.php'; + $res = []; + $res[] = ClassType::from(Abc\PropertyHookSignatures::class, withBodies: true); + $res[] = ClassType::from(Abc\AbstractHookSignatures::class, withBodies: true); + $res[] = ClassType::from(Abc\PropertyHookSignaturesChild::class, withBodies: true); + sameFile(__DIR__ . '/expected/ClassType.from.bodies.84.expect', implode("\n", $res)); +} diff --git a/tests/PhpGenerator/Extractor.extractPropertyHookBodies.phpt b/tests/PhpGenerator/Extractor.extractPropertyHookBodies.phpt new file mode 100644 index 00000000..39e53fc9 --- /dev/null +++ b/tests/PhpGenerator/Extractor.extractPropertyHookBodies.phpt @@ -0,0 +1,50 @@ + 'x'; + } + + public string $full { + get { + if (true) { + return 'x'; + } else { + return 'y'; + } + } + } + + public string $empty { + set { } + } + + abstract public string $abstract { get; } + } + + XX); + +$bodies = $extractor->extractPropertyHookBodies('NS\Undefined'); +Assert::same([], $bodies); + +$bodies = $extractor->extractPropertyHookBodies('NS\Foo'); +Assert::same([ + 'short' => ['get' => ["'x'", true]], + 'full' => [ + 'get' => ["if (true) {\n return 'x';\n} else {\n return 'y';\n}", false], + ], + 'empty' => ['set' => ['', false]], +], $bodies); diff --git a/tests/PhpGenerator/expected/ClassType.from.bodies.84.expect b/tests/PhpGenerator/expected/ClassType.from.bodies.84.expect new file mode 100644 index 00000000..62891892 --- /dev/null +++ b/tests/PhpGenerator/expected/ClassType.from.bodies.84.expect @@ -0,0 +1,88 @@ +class PropertyHookSignatures +{ + public string $basic { + get => 'x'; + } + + public string $fullGet { + get { + return 'x'; + } + } + + protected string $refGet { + &get { + return 'x'; + } + } + + protected string $finalGet { + final get => 'x'; + } + + public string $basicSet { + set => 'x'; + } + + public string $fullSet { + set { + } + } + + public string $setWithParam { + set(string $foo) { + } + } + + public string $setWithParam2 { + set(string|int $value) => ''; + } + + public string $finalSet { + final set { + } + } + + public string $combined { + set { + } + get => 'x'; + } + + final public string $combinedFinal { + /** comment set */ + #[Set] + set { + } + /** comment get */ + #[Get] + get => 'x'; + } + + public string $virtualProp { + set { + } + &get => 'x'; + } +} + +abstract class AbstractHookSignatures +{ + abstract public string $abstractGet { get; } + abstract protected string $abstractSet { set; } + abstract public string $abstractBoth { set; get; } + + abstract public string $mixedGet { + set => 'x'; + get; + } + + abstract public string $mixedSet { + set; + get => 'x'; + } +} + +class PropertyHookSignaturesChild extends PropertyHookSignatures +{ +} From eadf6e884f175a545819fcfa3955b58f6a1043ba Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 6 Jun 2025 01:04:13 +0200 Subject: [PATCH 247/266] composer: require stable packages outside of nette --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index cdd26cf6..4a2cbb89 100644 --- a/composer.json +++ b/composer.json @@ -22,8 +22,8 @@ "nette/tester": "^2.4", "nikic/php-parser": "^4.18 || ^5.0", "tracy/tracy": "^2.8", - "phpstan/phpstan": "^1.0", - "jetbrains/phpstorm-attributes": "dev-master" + "phpstan/phpstan-nette": "^2.0@stable", + "jetbrains/phpstorm-attributes": "^1.2" }, "suggest": { "nikic/php-parser": "to use ClassType::from(withBodies: true) & ClassType::fromCode()" From 0fe3b8e12c96fe42a96c80d8e39346875d1ae8f8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 6 Jun 2025 01:20:32 +0200 Subject: [PATCH 248/266] composer: added psr-4 loader --- composer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4a2cbb89..0c147498 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,10 @@ "nikic/php-parser": "to use ClassType::from(withBodies: true) & ClassType::fromCode()" }, "autoload": { - "classmap": ["src/"] + "classmap": ["src/"], + "psr-4": { + "Nette\\": "src" + } }, "minimum-stability": "dev", "scripts": { From 012e3e4625b2955d2c309c0bd4598971270e4e5d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 19 Jun 2025 19:44:00 +0200 Subject: [PATCH 249/266] optimized global function calls --- src/PhpGenerator/ClassLike.php | 1 + src/PhpGenerator/ClassManipulator.php | 1 + src/PhpGenerator/ClassType.php | 1 + src/PhpGenerator/Dumper.php | 2 ++ src/PhpGenerator/Extractor.php | 1 + src/PhpGenerator/Factory.php | 2 ++ src/PhpGenerator/Helpers.php | 1 + src/PhpGenerator/Method.php | 1 + src/PhpGenerator/PhpFile.php | 2 ++ src/PhpGenerator/PhpNamespace.php | 2 ++ src/PhpGenerator/Printer.php | 1 + src/PhpGenerator/Traits/FunctionLike.php | 1 + src/PhpGenerator/Traits/MethodsAware.php | 1 + src/PhpGenerator/Traits/PropertiesAware.php | 1 + src/PhpGenerator/Traits/PropertyLike.php | 1 + src/PhpGenerator/Traits/TraitsAware.php | 1 + src/PhpGenerator/Type.php | 1 + 17 files changed, 21 insertions(+) diff --git a/src/PhpGenerator/ClassLike.php b/src/PhpGenerator/ClassLike.php index a5ad5e8f..9cddc99b 100644 --- a/src/PhpGenerator/ClassLike.php +++ b/src/PhpGenerator/ClassLike.php @@ -10,6 +10,7 @@ namespace Nette\PhpGenerator; use Nette; +use function array_map, is_object, strtolower; /** diff --git a/src/PhpGenerator/ClassManipulator.php b/src/PhpGenerator/ClassManipulator.php index 86782958..4ad4cca4 100644 --- a/src/PhpGenerator/ClassManipulator.php +++ b/src/PhpGenerator/ClassManipulator.php @@ -10,6 +10,7 @@ namespace Nette\PhpGenerator; use Nette; +use const PHP_VERSION_ID; final class ClassManipulator diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 8e04e24f..2f93e858 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -10,6 +10,7 @@ namespace Nette\PhpGenerator; use Nette; +use function array_diff, array_map, strtolower; /** diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index a3aeafc7..834de47e 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -10,6 +10,8 @@ namespace Nette\PhpGenerator; use Nette; +use function addcslashes, array_keys, array_shift, count, dechex, implode, in_array, is_array, is_int, is_object, is_resource, is_string, ltrim, method_exists, ord, preg_match, preg_replace, preg_replace_callback, preg_split, range, serialize, str_contains, str_pad, str_repeat, str_replace, strlen, strrpos, strtoupper, substr, trim, unserialize, var_export; +use const PHP_VERSION_ID, PREG_SPLIT_DELIM_CAPTURE, STR_PAD_LEFT; /** diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 69f43c2f..901de5f4 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -15,6 +15,7 @@ use PhpParser\Node; use PhpParser\NodeFinder; use PhpParser\ParserFactory; +use function addcslashes, array_map, assert, class_exists, end, in_array, is_array, method_exists, rtrim, str_contains, str_repeat, str_replace, str_starts_with, strlen, substr, substr_replace, usort; /** diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 39e5ebab..efd04914 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -11,6 +11,8 @@ use Nette; use Nette\Utils\Reflection; +use function array_diff, array_filter, array_key_exists, array_map, count, explode, file_get_contents, implode, is_object, is_subclass_of, method_exists, reset; +use const PHP_VERSION_ID; /** diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index fe296991..656d686d 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -10,6 +10,7 @@ namespace Nette\PhpGenerator; use Nette; +use function is_string, preg_match, preg_replace, preg_replace_callback, str_contains, str_repeat, str_replace, strrpos, strtolower, substr, trim; /** diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index 92b8e00b..c2306fd5 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -10,6 +10,7 @@ namespace Nette\PhpGenerator; use Nette; +use function func_num_args; /** diff --git a/src/PhpGenerator/PhpFile.php b/src/PhpGenerator/PhpFile.php index f38a10b3..d1d81571 100644 --- a/src/PhpGenerator/PhpFile.php +++ b/src/PhpGenerator/PhpFile.php @@ -9,6 +9,8 @@ namespace Nette\PhpGenerator; +use function count; + /** * Definition of a PHP file. diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index e4d2df62..956b7431 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -11,6 +11,8 @@ use Nette; use Nette\InvalidStateException; +use function strlen; +use const ARRAY_FILTER_USE_BOTH; /** diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index ab3a9aee..960abf04 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -11,6 +11,7 @@ use Nette; use Nette\Utils\Strings; +use function array_filter, array_map, count, end, get_debug_type, implode, is_scalar, ltrim, preg_replace, rtrim, str_contains, str_repeat, str_replace, strlen, substr; /** diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index 8389ed26..8522abfb 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -14,6 +14,7 @@ use Nette\PhpGenerator\Dumper; use Nette\PhpGenerator\Parameter; use Nette\Utils\Type; +use function func_num_args; /** diff --git a/src/PhpGenerator/Traits/MethodsAware.php b/src/PhpGenerator/Traits/MethodsAware.php index f6f36534..9b38dc85 100644 --- a/src/PhpGenerator/Traits/MethodsAware.php +++ b/src/PhpGenerator/Traits/MethodsAware.php @@ -11,6 +11,7 @@ use Nette; use Nette\PhpGenerator\Method; +use function strtolower; /** diff --git a/src/PhpGenerator/Traits/PropertiesAware.php b/src/PhpGenerator/Traits/PropertiesAware.php index a87ab4d8..64418249 100644 --- a/src/PhpGenerator/Traits/PropertiesAware.php +++ b/src/PhpGenerator/Traits/PropertiesAware.php @@ -11,6 +11,7 @@ use Nette; use Nette\PhpGenerator\Property; +use function func_num_args; /** diff --git a/src/PhpGenerator/Traits/PropertyLike.php b/src/PhpGenerator/Traits/PropertyLike.php index 219c7b44..49858710 100644 --- a/src/PhpGenerator/Traits/PropertyLike.php +++ b/src/PhpGenerator/Traits/PropertyLike.php @@ -13,6 +13,7 @@ use Nette\PhpGenerator\PropertyHook; use Nette\PhpGenerator\PropertyHookType; use Nette\PhpGenerator\Visibility; +use function array_filter, in_array; /** diff --git a/src/PhpGenerator/Traits/TraitsAware.php b/src/PhpGenerator/Traits/TraitsAware.php index 7fb051be..61fddde8 100644 --- a/src/PhpGenerator/Traits/TraitsAware.php +++ b/src/PhpGenerator/Traits/TraitsAware.php @@ -11,6 +11,7 @@ use Nette; use Nette\PhpGenerator\TraitUse; +use function array_map, func_get_arg, func_num_args, is_array; /** diff --git a/src/PhpGenerator/Type.php b/src/PhpGenerator/Type.php index 0b519e57..8c20a6b8 100644 --- a/src/PhpGenerator/Type.php +++ b/src/PhpGenerator/Type.php @@ -10,6 +10,7 @@ namespace Nette\PhpGenerator; use Nette; +use function implode, preg_match, preg_replace, str_contains; /** From a3901b8b81cdc31fd512d3094bf713d2165c1771 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 1 Aug 2025 04:10:47 +0200 Subject: [PATCH 250/266] support for PHP 8.5 --- .github/workflows/tests.yml | 2 +- composer.json | 2 +- readme.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e4e361f3..3b8a00b7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['8.0', '8.1', '8.2', '8.3', '8.4'] + php: ['8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] fail-fast: false diff --git a/composer.json b/composer.json index 0c147498..8d5ba44d 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": "8.0 - 8.4", + "php": "8.0 - 8.5", "nette/utils": "^3.2.9 || ^4.0" }, "require-dev": { diff --git a/readme.md b/readme.md index ae6ef327..dfa48103 100644 --- a/readme.md +++ b/readme.md @@ -26,7 +26,7 @@ Download and install the library using the [Composer](https://doc.nette.org/en/b composer require nette/php-generator ``` -PhpGenerator 4.1 is compatible with PHP 8.0 to 8.4. Documentation can be found on the [library's website](https://doc.nette.org/php-generator). +PhpGenerator 4.1 is compatible with PHP 8.0 to 8.5. Documentation can be found on the [library's website](https://doc.nette.org/php-generator).   From 4c484a9bd66f29971a811dcaea1be0dea93cc425 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 6 Aug 2025 17:12:30 +0200 Subject: [PATCH 251/266] added support for asymmetric visibility for static property (has always existed) --- composer.json | 2 +- tests/PhpGenerator/ClassType.from.85.phpt | 17 +++++++++++++++++ tests/PhpGenerator/Extractor.extractAll.phpt | 3 +++ .../expected/ClassType.from.85.expect | 4 ++++ .../expected/Extractor.classes.85.expect | 10 ++++++++++ tests/PhpGenerator/fixtures/classes.85.php | 10 ++++++++++ 6 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 tests/PhpGenerator/ClassType.from.85.phpt create mode 100644 tests/PhpGenerator/expected/ClassType.from.85.expect create mode 100644 tests/PhpGenerator/expected/Extractor.classes.85.expect create mode 100644 tests/PhpGenerator/fixtures/classes.85.php diff --git a/composer.json b/composer.json index 8d5ba44d..04f1e18c 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.4 features.", + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.5 features.", "keywords": ["nette", "php", "code", "scaffolding"], "homepage": "https://nette.org", "license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"], diff --git a/tests/PhpGenerator/ClassType.from.85.phpt b/tests/PhpGenerator/ClassType.from.85.phpt new file mode 100644 index 00000000..1c7019ce --- /dev/null +++ b/tests/PhpGenerator/ClassType.from.85.phpt @@ -0,0 +1,17 @@ +extractAll(); sameFile(__DIR__ . '/expected/Extractor.classes.84.expect', (string) $file); + + $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.85.php')))->extractAll(); + sameFile(__DIR__ . '/expected/Extractor.classes.85.expect', (string) $file); } $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/enum.php')))->extractAll(); diff --git a/tests/PhpGenerator/expected/ClassType.from.85.expect b/tests/PhpGenerator/expected/ClassType.from.85.expect new file mode 100644 index 00000000..98d16299 --- /dev/null +++ b/tests/PhpGenerator/expected/ClassType.from.85.expect @@ -0,0 +1,4 @@ +class Class85 +{ + private(set) static string $foo; +} diff --git a/tests/PhpGenerator/expected/Extractor.classes.85.expect b/tests/PhpGenerator/expected/Extractor.classes.85.expect new file mode 100644 index 00000000..66dd097c --- /dev/null +++ b/tests/PhpGenerator/expected/Extractor.classes.85.expect @@ -0,0 +1,10 @@ + Date: Wed, 6 Aug 2025 16:38:01 +0200 Subject: [PATCH 252/266] added support for final promoted property --- src/PhpGenerator/Extractor.php | 6 ++++-- src/PhpGenerator/Factory.php | 3 ++- src/PhpGenerator/Printer.php | 5 ++++- src/PhpGenerator/Property.php | 14 -------------- src/PhpGenerator/Traits/PropertyLike.php | 14 ++++++++++++++ tests/PhpGenerator/PropertyLike.hooks.phpt | 11 +++++++++++ .../PhpGenerator/expected/ClassType.from.85.expect | 6 ++++++ .../expected/Extractor.classes.85.expect | 6 ++++++ tests/PhpGenerator/fixtures/classes.85.php | 5 +++++ 9 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 901de5f4..4308a65b 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -476,10 +476,12 @@ private function setupFunction(GlobalFunction|Method|PropertyHook $function, Nod foreach ($node->getParams() as $item) { $getVisibility = $this->toVisibility($item->flags); $setVisibility = $this->toSetterVisibility($item->flags); - if ($getVisibility || $setVisibility) { + $final = (bool) ($item->flags & Modifiers::FINAL); + if ($getVisibility || $setVisibility || $final) { $param = $function->addPromotedParameter($item->var->name) ->setVisibility($getVisibility, $setVisibility) - ->setReadonly((bool) ($item->flags & Node\Stmt\Class_::MODIFIER_READONLY)); + ->setReadonly((bool) ($item->flags & Node\Stmt\Class_::MODIFIER_READONLY)) + ->setFinal($final); $this->addHooksToProperty($param, $item); } else { $param = $function->addParameter($item->var->name); diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index efd04914..63c57cd4 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -211,7 +211,8 @@ public function fromParameterReflection(\ReflectionParameter $from): Parameter $property = $from->getDeclaringClass()->getProperty($from->name); $param = (new PromotedParameter($from->name)) ->setVisibility($this->getVisibility($property)) - ->setReadOnly(PHP_VERSION_ID >= 80100 && $property->isReadonly()); + ->setReadOnly(PHP_VERSION_ID >= 80100 && $property->isReadonly()) + ->setFinal(PHP_VERSION_ID >= 80500 && $property->isFinal() && !$property->isPrivateSet()); $this->addHooks($property, $param); } else { $param = new Parameter($from->name); diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 960abf04..872cbd83 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -345,7 +345,10 @@ private function formatParameters(Closure|GlobalFunction|Method|PropertyHook $fu $this->printDocComment($param) . ($attrs ? ($multiline ? substr($attrs, 0, -1) . "\n" : $attrs) : '') . ($param instanceof PromotedParameter - ? $this->printPropertyVisibility($param) . ($param->isReadOnly() && $param->getType() ? ' readonly' : '') . ' ' + ? ($param->isFinal() ? 'final ' : '') + . $this->printPropertyVisibility($param) + . ($param->isReadOnly() && $param->getType() ? ' readonly' : '') + . ' ' : '') . ltrim($this->printType($param->getType(), $param->isNullable()) . ' ') . ($param->isReference() ? '&' : '') diff --git a/src/PhpGenerator/Property.php b/src/PhpGenerator/Property.php index f8d6c72e..0586ee2f 100644 --- a/src/PhpGenerator/Property.php +++ b/src/PhpGenerator/Property.php @@ -28,7 +28,6 @@ final class Property private ?string $type = null; private bool $nullable = false; private bool $initialized = false; - private bool $final = false; private bool $abstract = false; @@ -101,19 +100,6 @@ public function isInitialized(): bool } - public function setFinal(bool $state = true): static - { - $this->final = $state; - return $this; - } - - - public function isFinal(): bool - { - return $this->final; - } - - public function setAbstract(bool $state = true): static { $this->abstract = $state; diff --git a/src/PhpGenerator/Traits/PropertyLike.php b/src/PhpGenerator/Traits/PropertyLike.php index 49858710..787f3555 100644 --- a/src/PhpGenerator/Traits/PropertyLike.php +++ b/src/PhpGenerator/Traits/PropertyLike.php @@ -23,6 +23,7 @@ trait PropertyLike { /** @var array{'set' => ?string, 'get' => ?string} */ private array $visibility = [PropertyAccessMode::Set => null, PropertyAccessMode::Get => null]; + private bool $final = false; private bool $readOnly = false; /** @var array */ @@ -95,6 +96,19 @@ public function isPrivate(string $mode = PropertyAccessMode::Get): bool } + public function setFinal(bool $state = true): static + { + $this->final = $state; + return $this; + } + + + public function isFinal(): bool + { + return $this->final; + } + + public function setReadOnly(bool $state = true): static { $this->readOnly = $state; diff --git a/tests/PhpGenerator/PropertyLike.hooks.phpt b/tests/PhpGenerator/PropertyLike.hooks.phpt index 51e6f56f..0b74a362 100644 --- a/tests/PhpGenerator/PropertyLike.hooks.phpt +++ b/tests/PhpGenerator/PropertyLike.hooks.phpt @@ -79,6 +79,14 @@ $method->addPromotedParameter('second') ->addParameter('value') ->setType('string'); +$method->addPromotedParameter('third') + ->setPublic() + ->setProtected('set') + ->setFinal() + ->setType('string') + ->addComment('hello') + ->addAttribute('Example'); + same(<<<'XX' class Demo { @@ -91,6 +99,9 @@ same(<<<'XX' public string $second { final set(string $value) => $value; }, + /** hello */ + #[Example] + final public protected(set) string $third, ) { } } diff --git a/tests/PhpGenerator/expected/ClassType.from.85.expect b/tests/PhpGenerator/expected/ClassType.from.85.expect index 98d16299..0f46e4c2 100644 --- a/tests/PhpGenerator/expected/ClassType.from.85.expect +++ b/tests/PhpGenerator/expected/ClassType.from.85.expect @@ -1,4 +1,10 @@ class Class85 { private(set) static string $foo; + + + public function __construct( + final public $final, + ) { + } } diff --git a/tests/PhpGenerator/expected/Extractor.classes.85.expect b/tests/PhpGenerator/expected/Extractor.classes.85.expect index 66dd097c..66acf331 100644 --- a/tests/PhpGenerator/expected/Extractor.classes.85.expect +++ b/tests/PhpGenerator/expected/Extractor.classes.85.expect @@ -7,4 +7,10 @@ namespace Abc; class Class85 { private(set) static string $foo; + + + function __construct( + final public $final, + ) { + } } diff --git a/tests/PhpGenerator/fixtures/classes.85.php b/tests/PhpGenerator/fixtures/classes.85.php index 66dd097c..9460b244 100644 --- a/tests/PhpGenerator/fixtures/classes.85.php +++ b/tests/PhpGenerator/fixtures/classes.85.php @@ -7,4 +7,9 @@ class Class85 { private(set) static string $foo; + + function __construct( + final $final, + ) { + } } From ad8a0f5e5026c9d6baa51a16c533096bab0472a8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 6 Aug 2025 18:11:31 +0200 Subject: [PATCH 253/266] updated PHP keywords [Closes #178] --- src/PhpGenerator/Helpers.php | 30 ++++++++++++---------------- tests/PhpGenerator/invalidNames.phpt | 5 ----- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 656d686d..be7ac85d 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -23,26 +23,22 @@ final class Helpers public const ReIdentifier = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'; public const Keywords = [ - // built-in types - 'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1, - 'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, 'mixed' => 1, 'false' => 1, - 'never' => 1, 'true' => 1, - // class keywords - 'self' => 1, 'parent' => 1, 'static' => 1, + 'bool' => 1, 'false' => 1, 'float' => 1, 'int' => 1, 'iterable' => 1, 'mixed' => 1, 'never' => 1, 'null' => 1, + 'object' => 1, 'parent' => 1, 'self' => 1, 'string' => 1, 'true' => 1, 'void' => 1, // PHP keywords - 'include' => 1, 'include_once' => 1, 'eval' => 1, 'require' => 1, 'require_once' => 1, 'or' => 1, 'xor' => 1, - 'and' => 1, 'instanceof' => 1, 'new' => 1, 'clone' => 1, 'exit' => 1, 'if' => 1, 'elseif' => 1, 'else' => 1, - 'endif' => 1, 'echo' => 1, 'do' => 1, 'while' => 1, 'endwhile' => 1, 'for' => 1, 'endfor' => 1, 'foreach' => 1, - 'endforeach' => 1, 'declare' => 1, 'enddeclare' => 1, 'as' => 1, 'try' => 1, 'catch' => 1, 'finally' => 1, - 'throw' => 1, 'use' => 1, 'insteadof' => 1, 'global' => 1, 'var' => 1, 'unset' => 1, 'isset' => 1, 'empty' => 1, - 'continue' => 1, 'goto' => 1, 'function' => 1, 'const' => 1, 'return' => 1, 'print' => 1, 'yield' => 1, 'list' => 1, - 'switch' => 1, 'endswitch' => 1, 'case' => 1, 'default' => 1, 'break' => 1, - 'extends' => 1, 'implements' => 1, 'namespace' => 1, 'trait' => 1, 'interface' => 1, 'class' => 1, '__CLASS__' => 1, - '__TRAIT__' => 1, '__FUNCTION__' => 1, '__METHOD__' => 1, '__LINE__' => 1, '__FILE__' => 1, '__DIR__' => 1, - '__NAMESPACE__' => 1, 'fn' => 1, 'match' => 1, 'enum' => 1, 'abstract' => 1, 'final' => 1, - 'private' => 1, 'protected' => 1, 'public' => 1, 'readonly' => 1, + '__halt_compiler' => 1, 'abstract' => 1, 'and' => 1, 'array' => 1, 'as' => 1, 'break' => 1, 'callable' => 1, + 'case' => 1, 'catch' => 1, 'class' => 1, 'clone' => 1, 'const' => 1, 'continue' => 1, 'declare' => 1, 'default' => 1, + 'die' => 1, 'do' => 1, 'echo' => 1, 'else' => 1, 'elseif' => 1, 'empty' => 1, 'enddeclare' => 1, 'endfor' => 1, + 'endforeach' => 1, 'endif' => 1, 'endswitch' => 1, 'endwhile' => 1, 'eval' => 1, 'exit' => 1, 'extends' => 1, + 'final' => 1, 'finally' => 1, 'fn' => 1, 'for' => 1, 'foreach' => 1, 'function' => 1, 'global' => 1, 'goto' => 1, + 'if' => 1, 'implements' => 1, 'include' => 1, 'include_once' => 1, 'instanceof' => 1, 'insteadof' => 1, + 'interface' => 1, 'isset' => 1, 'list' => 1, 'match' => 1, 'namespace' => 1, 'new' => 1, 'or' => 1, 'print' => 1, + 'private' => 1, 'protected' => 1, 'public' => 1, 'readonly' => 1, 'require' => 1, 'require_once' => 1, 'return' => 1, + 'static' => 1, 'switch' => 1, 'throw' => 1, 'trait' => 1, 'try' => 1, 'unset' => 1, 'use' => 1, 'var' => 1, + 'while' => 1, 'xor' => 1, 'yield' => 1, '__CLASS__' => 1, '__DIR__' => 1, '__FILE__' => 1, '__FUNCTION__' => 1, + '__LINE__' => 1, '__METHOD__' => 1, '__NAMESPACE__' => 1, '__PROPERTY__' => 1, '__TRAIT__' => 1, ]; /** @deprecated */ diff --git a/tests/PhpGenerator/invalidNames.phpt b/tests/PhpGenerator/invalidNames.phpt index 624656b5..33dade1c 100644 --- a/tests/PhpGenerator/invalidNames.phpt +++ b/tests/PhpGenerator/invalidNames.phpt @@ -87,11 +87,6 @@ Assert::exception( Nette\InvalidArgumentException::class, ); -Assert::exception( - fn() => new Nette\PhpGenerator\ClassType('enum'), - Nette\InvalidArgumentException::class, -); - Assert::exception( fn() => new Nette\PhpGenerator\ClassType('bool'), Nette\InvalidArgumentException::class, From 186a3f5a96c2e68bf83dc4fbb8a469ab419699f5 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 6 Aug 2025 18:36:23 +0200 Subject: [PATCH 254/266] improved typehints --- src/PhpGenerator/Factory.php | 3 ++- src/PhpGenerator/Literal.php | 1 + src/PhpGenerator/Method.php | 3 +++ src/PhpGenerator/PropertyHook.php | 10 ++++++++-- src/PhpGenerator/Traits/PropertyLike.php | 2 +- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 63c57cd4..fca18ca2 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -342,6 +342,7 @@ public function fromCode(string $code): PhpFile } + /** @return Attribute[] */ private function getAttributes($from): array { return array_map(function ($attr) { @@ -357,7 +358,7 @@ private function getAttributes($from): array } - private function getVisibility($from): string + private function getVisibility(\ReflectionProperty|\ReflectionMethod|\ReflectionClassConstant $from): string { return $from->isPrivate() ? Visibility::Private diff --git a/src/PhpGenerator/Literal.php b/src/PhpGenerator/Literal.php index d48f6a4e..3171cb9c 100644 --- a/src/PhpGenerator/Literal.php +++ b/src/PhpGenerator/Literal.php @@ -17,6 +17,7 @@ class Literal { /** * Creates a literal representing the creation of an object using the new operator. + * @param mixed[] $args */ public static function new(string $class, array $args = []): self { diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index c2306fd5..5816fbe0 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -31,6 +31,9 @@ final class Method private bool $abstract = false; + /** + * @param string|array{object|string, string}|\Closure $method + */ public static function from(string|array|\Closure $method): static { return (new Factory)->fromMethodReflection(Nette\Utils\Callback::toReflection($method)); diff --git a/src/PhpGenerator/PropertyHook.php b/src/PhpGenerator/PropertyHook.php index f65e22ac..8884a8b7 100644 --- a/src/PhpGenerator/PropertyHook.php +++ b/src/PhpGenerator/PropertyHook.php @@ -79,7 +79,10 @@ public function isAbstract(): bool } - /** @internal */ + /** + * @param Parameter[] $val + * @internal + */ public function setParameters(array $val): static { (function (Parameter ...$val) {})(...$val); @@ -92,7 +95,10 @@ public function setParameters(array $val): static } - /** @internal */ + /** + * @return Parameter[] + * @internal + */ public function getParameters(): array { return $this->parameters; diff --git a/src/PhpGenerator/Traits/PropertyLike.php b/src/PhpGenerator/Traits/PropertyLike.php index 787f3555..7ed4b9d9 100644 --- a/src/PhpGenerator/Traits/PropertyLike.php +++ b/src/PhpGenerator/Traits/PropertyLike.php @@ -21,7 +21,7 @@ */ trait PropertyLike { - /** @var array{'set' => ?string, 'get' => ?string} */ + /** @var array{set: ?string, get: ?string} */ private array $visibility = [PropertyAccessMode::Set => null, PropertyAccessMode::Get => null]; private bool $final = false; private bool $readOnly = false; From 1ecb07bb6f467b30051c1b65976bd8ccac551217 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 6 Aug 2025 18:50:46 +0200 Subject: [PATCH 255/266] small fixes --- src/PhpGenerator/Extractor.php | 10 +++------- src/PhpGenerator/Factory.php | 5 ++++- src/PhpGenerator/PhpNamespace.php | 2 +- src/PhpGenerator/Printer.php | 2 +- .../Extractor.extractPropertyHookBodies.phpt | 3 +++ 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 4308a65b..7716d355 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -70,7 +70,6 @@ public function extractMethodBodies(string $className): array $res = []; foreach ($nodeFinder->findInstanceOf($classNode, Node\Stmt\ClassMethod::class) as $methodNode) { - assert($methodNode instanceof Node\Stmt\ClassMethod); if ($methodNode->stmts) { $res[$methodNode->name->toString()] = $this->getReformattedContents($methodNode->stmts, 2); } @@ -110,7 +109,7 @@ public function extractPropertyHookBodies(string $className): array } - public function extractFunctionBody(string $name): ?string + public function extractFunctionBody(string $name): string { $functionNode = (new NodeFinder)->findFirst( $this->statements, @@ -250,7 +249,6 @@ public function extractAll(): PhpFile $namespaces = ['' => $this->statements]; foreach ($this->statements as $node) { if ($node instanceof Node\Stmt\Declare_ - && $node->declares[0] instanceof Node\Stmt\DeclareDeclare && $node->declares[0]->key->name === 'strict_types' && $node->declares[0]->value instanceof Node\Scalar\LNumber ) { @@ -344,6 +342,7 @@ private function addTraitToClass(ClassLike $class, Node\Stmt\TraitUse $node): vo foreach ($node->traits as $item) { $trait = $class->addTrait($item->toString()); } + assert($trait instanceof TraitUse); foreach ($node->adaptations as $item) { $trait->addResolution(rtrim($this->getReformattedContents([$item], 0), ';')); @@ -528,10 +527,7 @@ private function toValue(Node\Expr $node): mixed return new Literal($this->getReformattedContents([$node], 0)); } elseif ($item->key) { - $key = $item->key instanceof Node\Identifier - ? $item->key->name - : $this->toValue($item->key); - + $key = $this->toValue($item->key); if ($key instanceof Literal) { return new Literal($this->getReformattedContents([$node], 0)); } diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index fca18ca2..e4755b4e 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -128,7 +128,10 @@ public function fromClassReflection( $class->setMethods($methods); foreach ($from->getTraitNames() as $trait) { - $class->addTrait($trait, $resolutions); + $trait = $class->addTrait($trait); + foreach ($resolutions as $resolution) { + $trait->addResolution($resolution); + } $resolutions = []; } diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 956b7431..229e46cc 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -167,7 +167,7 @@ public function getUses(string $of = self::NameNormal): array uasort($this->aliases[$of], fn(string $a, string $b): int => strtr($a, '\\', ' ') <=> strtr($b, '\\', ' ')); return array_filter( $this->aliases[$of], - fn($name, $alias) => strcasecmp(($this->name ? $this->name . '\\' : '') . $alias, $name), + fn($name, $alias) => (bool) strcasecmp(($this->name ? $this->name . '\\' : '') . $alias, $name), ARRAY_FILTER_USE_BOTH, ); } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 872cbd83..3d629907 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -480,7 +480,7 @@ private function printHooks(Property|PromotedParameter $property, bool $isInterf } $simple = true; - foreach ($property->getHooks() as $type => $hook) { + foreach ($hooks as $type => $hook) { $simple = $simple && ($hook->isAbstract() || $isInterface); $hooks[$type] = $this->printDocComment($hook) . $this->printAttributes($hook->getAttributes()) diff --git a/tests/PhpGenerator/Extractor.extractPropertyHookBodies.phpt b/tests/PhpGenerator/Extractor.extractPropertyHookBodies.phpt index 39e53fc9..d7dd0e97 100644 --- a/tests/PhpGenerator/Extractor.extractPropertyHookBodies.phpt +++ b/tests/PhpGenerator/Extractor.extractPropertyHookBodies.phpt @@ -7,6 +7,9 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; +if (!class_exists(PhpParser\Node\PropertyHook::class)) { + Tester\Environment::skip('Requires newer PhpParser.'); +} $extractor = new Extractor(<<<'XX' Date: Wed, 6 Aug 2025 17:02:11 +0200 Subject: [PATCH 256/266] opened 4.2-dev --- composer.json | 2 +- readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 04f1e18c..6a13d2e1 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,7 @@ }, "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/readme.md b/readme.md index dfa48103..b595dd8f 100644 --- a/readme.md +++ b/readme.md @@ -26,7 +26,7 @@ Download and install the library using the [Composer](https://doc.nette.org/en/b composer require nette/php-generator ``` -PhpGenerator 4.1 is compatible with PHP 8.0 to 8.5. Documentation can be found on the [library's website](https://doc.nette.org/php-generator). +PhpGenerator 4.2 is compatible with PHP 8.0 to 8.5. Documentation can be found on the [library's website](https://doc.nette.org/php-generator).   From 2d80458cab6c3b2be082cff3703f8509ce1d9069 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 6 Aug 2025 17:02:43 +0200 Subject: [PATCH 257/266] requires PHP 8.1 --- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 6 +++--- composer.json | 2 +- readme.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 1b855cdb..396244b2 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: 8.1 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3b8a00b7..8ed81e7e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] + php: ['8.1', '8.2', '8.3', '8.4', '8.5'] fail-fast: false @@ -35,7 +35,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: 8.1 coverage: none - run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: 8.1 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/composer.json b/composer.json index 6a13d2e1..af7ed972 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": "8.0 - 8.5", + "php": "8.1 - 8.5", "nette/utils": "^3.2.9 || ^4.0" }, "require-dev": { diff --git a/readme.md b/readme.md index b595dd8f..bb7fb17a 100644 --- a/readme.md +++ b/readme.md @@ -26,7 +26,7 @@ Download and install the library using the [Composer](https://doc.nette.org/en/b composer require nette/php-generator ``` -PhpGenerator 4.2 is compatible with PHP 8.0 to 8.5. Documentation can be found on the [library's website](https://doc.nette.org/php-generator). +PhpGenerator 4.2 is compatible with PHP 8.1 to 8.5. Documentation can be found on the [library's website](https://doc.nette.org/php-generator).   From 407ea5995d1d2a3b7f5561a452d91d64b064ea72 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 6 Aug 2025 20:24:30 +0200 Subject: [PATCH 258/266] removed PHP 8.0 stuff --- src/PhpGenerator/Dumper.php | 6 ++---- src/PhpGenerator/Factory.php | 8 ++++---- tests/PhpGenerator/ClassType.from.81.phpt | 4 ---- tests/PhpGenerator/Dumper.dump().enum.phpt | 1 - tests/PhpGenerator/Dumper.dump().phpt | 12 +++--------- tests/PhpGenerator/EnumType.from.phpt | 4 ---- tests/PhpGenerator/GlobalFunction.from.81.phpt | 4 ---- tests/PhpGenerator/Method.from.81.phpt | 4 ---- 8 files changed, 9 insertions(+), 34 deletions(-) diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 834de47e..7745576c 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -11,7 +11,7 @@ use Nette; use function addcslashes, array_keys, array_shift, count, dechex, implode, in_array, is_array, is_int, is_object, is_resource, is_string, ltrim, method_exists, ord, preg_match, preg_replace, preg_replace_callback, preg_split, range, serialize, str_contains, str_pad, str_repeat, str_replace, strlen, strrpos, strtoupper, substr, trim, unserialize, var_export; -use const PHP_VERSION_ID, PREG_SPLIT_DELIM_CAPTURE, STR_PAD_LEFT; +use const PREG_SPLIT_DELIM_CAPTURE, STR_PAD_LEFT; /** @@ -160,9 +160,7 @@ private function dumpObject(object $var, array $parents, int $level, int $column } elseif ($var instanceof \Closure) { $inner = Nette\Utils\Callback::unwrap($var); if (Nette\Utils\Callback::isStatic($inner)) { - return PHP_VERSION_ID < 80100 - ? '\Closure::fromCallable(' . $this->dump($inner) . ')' - : implode('::', (array) $inner) . '(...)'; + return implode('::', (array) $inner) . '(...)'; } throw new Nette\InvalidStateException('Cannot dump object of type Closure.'); diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index e4755b4e..7e4f125f 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -38,7 +38,7 @@ public function fromClassReflection( } $enumIface = null; - if (PHP_VERSION_ID >= 80100 && $from->isEnum()) { + if ($from->isEnum()) { $class = new EnumType($from->getShortName(), new PhpNamespace($from->getNamespaceName())); $from = new \ReflectionEnum($from->getName()); $enumIface = $from->isBacked() ? \BackedEnum::class : \UnitEnum::class; @@ -214,7 +214,7 @@ public function fromParameterReflection(\ReflectionParameter $from): Parameter $property = $from->getDeclaringClass()->getProperty($from->name); $param = (new PromotedParameter($from->name)) ->setVisibility($this->getVisibility($property)) - ->setReadOnly(PHP_VERSION_ID >= 80100 && $property->isReadonly()) + ->setReadOnly($property->isReadonly()) ->setFinal(PHP_VERSION_ID >= 80500 && $property->isFinal() && !$property->isPrivateSet()); $this->addHooks($property, $param); } else { @@ -248,7 +248,7 @@ public function fromConstantReflection(\ReflectionClassConstant $from): Constant $const = new Constant($from->name); $const->setValue($from->getValue()); $const->setVisibility($this->getVisibility($from)); - $const->setFinal(PHP_VERSION_ID >= 80100 && $from->isFinal()); + $const->setFinal($from->isFinal()); $const->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); $const->setAttributes($this->getAttributes($from)); return $const; @@ -274,7 +274,7 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property $prop->setVisibility($this->getVisibility($from)); $prop->setType((string) $from->getType()); $prop->setInitialized($from->hasType() && array_key_exists($prop->getName(), $defaults)); - $prop->setReadOnly(PHP_VERSION_ID >= 80100 && $from->isReadOnly()); + $prop->setReadOnly($from->isReadOnly()); $prop->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); $prop->setAttributes($this->getAttributes($from)); diff --git a/tests/PhpGenerator/ClassType.from.81.phpt b/tests/PhpGenerator/ClassType.from.81.phpt index ce30dfc1..ff5a1f5b 100644 --- a/tests/PhpGenerator/ClassType.from.81.phpt +++ b/tests/PhpGenerator/ClassType.from.81.phpt @@ -1,9 +1,5 @@ 4,\n\t'a' => 1,\n\t\"\\x00*\\x00b\" => 2,\n])" - : "\\Nette\\PhpGenerator\\Dumper::createObject(\\Test2::class, [\n\t'a' => 1,\n\t\"\\x00*\\x00b\" => 2,\n\t\"\\x00Test2\\x00c\" => 4,\n])", + "\\Nette\\PhpGenerator\\Dumper::createObject(\\Test2::class, [\n\t'a' => 1,\n\t\"\\x00*\\x00b\" => 2,\n\t\"\\x00Test2\\x00c\" => 4,\n])", $dumper->dump(new Test2), ); Assert::equal(new Test2, eval('return ' . $dumper->dump(new Test2) . ';')); @@ -134,16 +132,12 @@ Assert::exception(function () { // closures Assert::same( - PHP_VERSION_ID < 80100 - ? "\\Closure::fromCallable('strlen')" - : 'strlen(...)', + 'strlen(...)', $dumper->dump(Closure::fromCallable('strlen')), ); Assert::same( - PHP_VERSION_ID < 80100 - ? "\\Closure::fromCallable(['Nette\\PhpGenerator\\ClassLike', 'from'])" - : 'Nette\PhpGenerator\ClassLike::from(...)', + 'Nette\PhpGenerator\ClassLike::from(...)', $dumper->dump(Closure::fromCallable([Nette\PhpGenerator\ClassLike::class, 'from'])), ); diff --git a/tests/PhpGenerator/EnumType.from.phpt b/tests/PhpGenerator/EnumType.from.phpt index 7dadafd2..977e7c5a 100644 --- a/tests/PhpGenerator/EnumType.from.phpt +++ b/tests/PhpGenerator/EnumType.from.phpt @@ -1,9 +1,5 @@ Date: Fri, 29 Nov 2024 02:26:45 +0100 Subject: [PATCH 259/266] requires nikic/php-parser 5.0 or newer --- composer.json | 2 +- src/PhpGenerator/Extractor.php | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index af7ed972..f745c862 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ }, "require-dev": { "nette/tester": "^2.4", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^5.0", "tracy/tracy": "^2.8", "phpstan/phpstan-nette": "^2.0@stable", "jetbrains/phpstorm-attributes": "^1.2" diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 7716d355..21a56975 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -15,7 +15,7 @@ use PhpParser\Node; use PhpParser\NodeFinder; use PhpParser\ParserFactory; -use function addcslashes, array_map, assert, class_exists, end, in_array, is_array, method_exists, rtrim, str_contains, str_repeat, str_replace, str_starts_with, strlen, substr, substr_replace, usort; +use function addcslashes, array_map, assert, class_exists, end, in_array, is_array, rtrim, str_contains, str_repeat, str_replace, str_starts_with, strlen, substr, substr_replace, usort; /** @@ -293,7 +293,7 @@ private function addClassLikeToFile(PhpFile $phpFile, Node\Stmt\ClassLike $node) $class = $phpFile->addClass($node->namespacedName->toString()); $class->setFinal($node->isFinal()); $class->setAbstract($node->isAbstract()); - $class->setReadOnly(method_exists($node, 'isReadonly') && $node->isReadonly()); + $class->setReadOnly($node->isReadonly()); if ($node->extends) { $class->setExtends($node->extends->toString()); } @@ -363,11 +363,11 @@ private function addPropertyToClass(ClassLike $class, Node\Stmt\Property $node): $prop->setValue($this->toValue($item->default)); } - $prop->setReadOnly((method_exists($node, 'isReadonly') && $node->isReadonly()) || ($class instanceof ClassType && $class->isReadOnly())); + $prop->setReadOnly($node->isReadonly() || ($class instanceof ClassType && $class->isReadOnly())); $this->addCommentAndAttributes($prop, $node); - $prop->setAbstract((bool) ($node->flags & Node\Stmt\Class_::MODIFIER_ABSTRACT)); - $prop->setFinal((bool) ($node->flags & Node\Stmt\Class_::MODIFIER_FINAL)); + $prop->setAbstract((bool) ($node->flags & Modifiers::ABSTRACT)); + $prop->setFinal((bool) ($node->flags & Modifiers::FINAL)); $this->addHooksToProperty($prop, $node); } } @@ -411,7 +411,7 @@ private function addConstantToClass(ClassLike $class, Node\Stmt\ClassConst $node foreach ($node->consts as $item) { $const = $class->addConstant($item->name->toString(), $this->toValue($item->value)); $const->setVisibility($this->toVisibility($node->flags)); - $const->setFinal(method_exists($node, 'isFinal') && $node->isFinal()); + $const->setFinal($node->isFinal()); $this->addCommentAndAttributes($const, $node); } } @@ -479,7 +479,7 @@ private function setupFunction(GlobalFunction|Method|PropertyHook $function, Nod if ($getVisibility || $setVisibility || $final) { $param = $function->addPromotedParameter($item->var->name) ->setVisibility($getVisibility, $setVisibility) - ->setReadonly((bool) ($item->flags & Node\Stmt\Class_::MODIFIER_READONLY)) + ->setReadonly($item->isReadonly()) ->setFinal($final); $this->addHooksToProperty($param, $item); } else { @@ -549,9 +549,9 @@ private function toValue(Node\Expr $node): mixed private function toVisibility(int $flags): ?string { return match (true) { - (bool) ($flags & Node\Stmt\Class_::MODIFIER_PUBLIC) => Visibility::Public, - (bool) ($flags & Node\Stmt\Class_::MODIFIER_PROTECTED) => Visibility::Protected, - (bool) ($flags & Node\Stmt\Class_::MODIFIER_PRIVATE) => Visibility::Private, + (bool) ($flags & Modifiers::PUBLIC) => Visibility::Public, + (bool) ($flags & Modifiers::PROTECTED) => Visibility::Protected, + (bool) ($flags & Modifiers::PRIVATE) => Visibility::Private, default => null, }; } From 323765c6950109f6c677d0f6dba2290e6d4f0a80 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 18 Jun 2025 20:57:37 +0200 Subject: [PATCH 260/266] uses nette/utils 4 --- composer.json | 2 +- src/PhpGenerator/Dumper.php | 2 +- src/PhpGenerator/Extractor.php | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index f745c862..4a98ff24 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ ], "require": { "php": "8.1 - 8.5", - "nette/utils": "^3.2.9 || ^4.0" + "nette/utils": "^4.0.6" }, "require-dev": { "nette/tester": "^2.4", diff --git a/src/PhpGenerator/Dumper.php b/src/PhpGenerator/Dumper.php index 7745576c..45ffc451 100644 --- a/src/PhpGenerator/Dumper.php +++ b/src/PhpGenerator/Dumper.php @@ -208,7 +208,7 @@ private function dumpCustomObject(object $var, array $parents, int $level): stri private function dumpLiteral(Literal $var, int $level): string { $s = $var->formatWith($this); - $s = Nette\Utils\Strings::normalizeNewlines($s); + $s = Nette\Utils\Strings::unixNewLines($s); $s = Nette\Utils\Strings::indent(trim($s), $level, $this->indentation); return ltrim($s, $this->indentation); } diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 21a56975..761777d7 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -48,7 +48,7 @@ private function parseCode(string $code): void throw new Nette\InvalidStateException('The input string is not a PHP code.'); } - $this->code = Nette\Utils\Strings::normalizeNewlines($code); + $this->code = Nette\Utils\Strings::unixNewLines($code); $parser = (new ParserFactory)->createForNewestSupportedVersion(); $stmts = $parser->parse($this->code); @@ -314,6 +314,8 @@ private function addClassLikeToFile(PhpFile $phpFile, Node\Stmt\ClassLike $node) foreach ($node->implements as $item) { $class->addImplement($item->toString()); } + } else { + throw new Nette\ShouldNotHappenException; } $this->addCommentAndAttributes($class, $node); From 342cd2a1c90730bd5280f591d8e4e72b6a6be008 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 6 Aug 2025 19:38:53 +0200 Subject: [PATCH 261/266] removed deprecated stuff --- src/PhpGenerator/ClassManipulator.php | 8 ------ src/PhpGenerator/ClassType.php | 40 --------------------------- 2 files changed, 48 deletions(-) diff --git a/src/PhpGenerator/ClassManipulator.php b/src/PhpGenerator/ClassManipulator.php index 4ad4cca4..08ebcaf8 100644 --- a/src/PhpGenerator/ClassManipulator.php +++ b/src/PhpGenerator/ClassManipulator.php @@ -121,12 +121,4 @@ private function implementProperty(\ReflectionProperty $rp): Property $this->class->addMember($property); return $property; } - - - /** @deprecated use implement() */ - public function implementInterface(string $interfaceName): void - { - trigger_error(__METHOD__ . '() is deprecated, use implement()', E_USER_DEPRECATED); - $this->implement($interfaceName); - } } diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index 2f93e858..f5f69c6f 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -39,38 +39,6 @@ final class ClassType extends ClassLike private array $implements = []; - /** @deprecated create object using 'new Nette\PhpGenerator\ClassType' */ - public static function class(?string $name): self - { - trigger_error(__METHOD__ . "() is deprecated, create object using 'new Nette\\PhpGenerator\\ClassType", E_USER_DEPRECATED); - return new self($name); - } - - - /** @deprecated create object using 'new Nette\PhpGenerator\InterfaceType' */ - public static function interface(string $name): InterfaceType - { - trigger_error(__METHOD__ . "() is deprecated, create object using 'new Nette\\PhpGenerator\\InterfaceType'", E_USER_DEPRECATED); - return new InterfaceType($name); - } - - - /** @deprecated create object using 'new Nette\PhpGenerator\TraitType' */ - public static function trait(string $name): TraitType - { - trigger_error(__METHOD__ . "() is deprecated, create object using 'new Nette\\PhpGenerator\\TraitType'", E_USER_DEPRECATED); - return new TraitType($name); - } - - - /** @deprecated create object using 'new Nette\PhpGenerator\EnumType' */ - public static function enum(string $name): EnumType - { - trigger_error(__METHOD__ . "() is deprecated, create object using 'new Nette\\PhpGenerator\\EnumType'", E_USER_DEPRECATED); - return new EnumType($name); - } - - public function __construct(?string $name = null, ?PhpNamespace $namespace = null) { if ($name === null) { @@ -82,14 +50,6 @@ public function __construct(?string $name = null, ?PhpNamespace $namespace = nul } - /** @deprecated */ - public function getType(): string - { - trigger_error(__METHOD__ . "() is deprecated, method always returns 'class'", E_USER_DEPRECATED); - return self::TYPE_CLASS; - } - - public function setFinal(bool $state = true): static { $this->final = $state; From 0698ed4d32a8b9ad902af127fabcfaa09adbd8d3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 29 Nov 2024 02:28:55 +0100 Subject: [PATCH 262/266] used attribute Deprecated --- src/PhpGenerator/ClassLike.php | 6 +++--- src/PhpGenerator/ClassType.php | 2 +- src/PhpGenerator/Helpers.php | 2 +- src/PhpGenerator/PhpNamespace.php | 6 +++--- src/PhpGenerator/Type.php | 32 +++++++++++++++---------------- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/PhpGenerator/ClassLike.php b/src/PhpGenerator/ClassLike.php index 9cddc99b..e8af5d24 100644 --- a/src/PhpGenerator/ClassLike.php +++ b/src/PhpGenerator/ClassLike.php @@ -21,15 +21,15 @@ abstract class ClassLike use Traits\CommentAware; use Traits\AttributeAware; - /** @deprecated use Visibility::Public */ + #[\Deprecated('Use Visibility::Public')] public const VisibilityPublic = Visibility::Public, VISIBILITY_PUBLIC = Visibility::Public; - /** @deprecated use Visibility::Protected */ + #[\Deprecated('Use Visibility::Protected')] public const VisibilityProtected = Visibility::Protected, VISIBILITY_PROTECTED = Visibility::Protected; - /** @deprecated use Visibility::Private */ + #[\Deprecated('Use Visibility::Private')] public const VisibilityPrivate = Visibility::Private, VISIBILITY_PRIVATE = Visibility::Private; diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index f5f69c6f..320c5067 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -23,7 +23,7 @@ final class ClassType extends ClassLike use Traits\PropertiesAware; use Traits\TraitsAware; - /** @deprecated */ + #[\Deprecated] public const TYPE_CLASS = 'class', TYPE_INTERFACE = 'interface', diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index be7ac85d..4047933e 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -41,7 +41,7 @@ final class Helpers '__LINE__' => 1, '__METHOD__' => 1, '__NAMESPACE__' => 1, '__PROPERTY__' => 1, '__TRAIT__' => 1, ]; - /** @deprecated */ + #[\Deprecated] public const PHP_IDENT = self::ReIdentifier, KEYWORDS = self::Keywords; diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 229e46cc..ffc46bd2 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -30,13 +30,13 @@ final class PhpNamespace NameFunction = 'f', NameConstant = 'c'; - /** @deprecated use PhpNamespace::NameNormal */ + #[\Deprecated('use PhpNamespace::NameNormal')] public const NAME_NORMAL = self::NameNormal; - /** @deprecated use PhpNamespace::NameFunction */ + #[\Deprecated('use PhpNamespace::NameFunction')] public const NAME_FUNCTION = self::NameFunction; - /** @deprecated use PhpNamespace::NameConstant */ + #[\Deprecated('use PhpNamespace::NameConstant')] public const NAME_CONSTANT = self::NameConstant; private string $name; diff --git a/src/PhpGenerator/Type.php b/src/PhpGenerator/Type.php index 8c20a6b8..42cb20b4 100644 --- a/src/PhpGenerator/Type.php +++ b/src/PhpGenerator/Type.php @@ -37,52 +37,52 @@ class Type Parent = 'parent', Static = 'static'; - /** @deprecated use Type::String */ + #[\Deprecated('use Type::String')] public const STRING = self::String; - /** @deprecated use Type::Int */ + #[\Deprecated('use Type::Int')] public const INT = self::Int; - /** @deprecated use Type::Float */ + #[\Deprecated('use Type::Float')] public const FLOAT = self::Float; - /** @deprecated use Type::Bool */ + #[\Deprecated('use Type::Bool')] public const BOOL = self::Bool; - /** @deprecated use Type::Array */ + #[\Deprecated('use Type::Array')] public const ARRAY = self::Array; - /** @deprecated use Type::Object */ + #[\Deprecated('use Type::Object')] public const OBJECT = self::Object; - /** @deprecated use Type::Callable */ + #[\Deprecated('use Type::Callable')] public const CALLABLE = self::Callable; - /** @deprecated use Type::Iterable */ + #[\Deprecated('use Type::Iterable')] public const ITERABLE = self::Iterable; - /** @deprecated use Type::Void */ + #[\Deprecated('use Type::Void')] public const VOID = self::Void; - /** @deprecated use Type::Never */ + #[\Deprecated('use Type::Never')] public const NEVER = self::Never; - /** @deprecated use Type::Mixed */ + #[\Deprecated('use Type::Mixed')] public const MIXED = self::Mixed; - /** @deprecated use Type::False */ + #[\Deprecated('use Type::False')] public const FALSE = self::False; - /** @deprecated use Type::Null */ + #[\Deprecated('use Type::Null')] public const NULL = self::Null; - /** @deprecated use Type::Self */ + #[\Deprecated('use Type::Self')] public const SELF = self::Self; - /** @deprecated use Type::Parent */ + #[\Deprecated('use Type::Parent')] public const PARENT = self::Parent; - /** @deprecated use Type::Static */ + #[\Deprecated('use Type::Static')] public const STATIC = self::Static; From 72329443ee2cd69b6adb3e25f680e8dce705c17f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 6 Aug 2025 18:54:12 +0200 Subject: [PATCH 263/266] addTrait() second argument is deprecated (BC break) --- src/PhpGenerator/Traits/TraitsAware.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PhpGenerator/Traits/TraitsAware.php b/src/PhpGenerator/Traits/TraitsAware.php index 61fddde8..8a1938d2 100644 --- a/src/PhpGenerator/Traits/TraitsAware.php +++ b/src/PhpGenerator/Traits/TraitsAware.php @@ -56,6 +56,7 @@ public function addTrait(string $name): TraitUse } $this->traits[$name] = $trait = new TraitUse($name); if (func_num_args() > 1 && is_array(func_get_arg(1))) { // back compatibility + trigger_error('Passing second argument to ' . __METHOD__ . '() is deprecated, use addResolution() instead.'); array_map(fn($item) => $trait->addResolution($item), func_get_arg(1)); } From 2fc2addf1f67bbfd9e985f5dca548d530775e8d2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 7 Mar 2024 23:26:38 +0100 Subject: [PATCH 264/266] ClassLike::from() & fromCode() returns 'static' --- src/PhpGenerator/ClassLike.php | 8 ++++---- tests/PhpGenerator/ClassLike.typecheck.phpt | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/PhpGenerator/ClassLike.php b/src/PhpGenerator/ClassLike.php index e8af5d24..e9edcbe0 100644 --- a/src/PhpGenerator/ClassLike.php +++ b/src/PhpGenerator/ClassLike.php @@ -37,27 +37,27 @@ abstract class ClassLike private ?string $name; - public static function from(string|object $class, bool $withBodies = false): self + public static function from(string|object $class, bool $withBodies = false): static { $instance = (new Factory) ->fromClassReflection(new \ReflectionClass($class), $withBodies); if (!$instance instanceof static) { $class = is_object($class) ? $class::class : $class; - trigger_error("$class cannot be represented with " . static::class . '. Call ' . $instance::class . '::' . __FUNCTION__ . '() or ' . __METHOD__ . '() instead.', E_USER_WARNING); + throw new Nette\InvalidArgumentException("$class cannot be represented with " . static::class . '. Call ' . $instance::class . '::' . __FUNCTION__ . '() or ' . __METHOD__ . '() instead.'); } return $instance; } - public static function fromCode(string $code): self + public static function fromCode(string $code): static { $instance = (new Factory) ->fromClassCode($code); if (!$instance instanceof static) { - trigger_error('Provided code cannot be represented with ' . static::class . '. Call ' . $instance::class . '::' . __FUNCTION__ . '() or ' . __METHOD__ . '() instead.', E_USER_WARNING); + throw new Nette\InvalidArgumentException('Provided code cannot be represented with ' . static::class . '. Call ' . $instance::class . '::' . __FUNCTION__ . '() or ' . __METHOD__ . '() instead.'); } return $instance; diff --git a/tests/PhpGenerator/ClassLike.typecheck.phpt b/tests/PhpGenerator/ClassLike.typecheck.phpt index 9b681c80..f8e03e5d 100644 --- a/tests/PhpGenerator/ClassLike.typecheck.phpt +++ b/tests/PhpGenerator/ClassLike.typecheck.phpt @@ -11,26 +11,26 @@ require __DIR__ . '/../bootstrap.php'; require __DIR__ . '/fixtures/classes.php'; -Assert::error( +Assert::exception( fn() => ClassType::from(Abc\Interface1::class), - E_USER_WARNING, + Nette\InvalidArgumentException::class, 'Abc\Interface1 cannot be represented with Nette\PhpGenerator\ClassType. Call Nette\PhpGenerator\InterfaceType::from() or Nette\PhpGenerator\ClassLike::from() instead.', ); -Assert::error( +Assert::exception( fn() => TraitType::from(Abc\Class1::class), - E_USER_WARNING, + Nette\InvalidArgumentException::class, 'Abc\Class1 cannot be represented with Nette\PhpGenerator\TraitType. Call Nette\PhpGenerator\ClassType::from() or Nette\PhpGenerator\ClassLike::from() instead.', ); -Assert::error( +Assert::exception( fn() => ClassType::fromCode(' InterfaceType::fromCode(' Date: Fri, 29 Nov 2024 01:05:18 +0100 Subject: [PATCH 265/266] Visibility, PropertyHookType & PropertyAccessMode are enums (BC break) --- src/PhpGenerator/Extractor.php | 4 +- src/PhpGenerator/Factory.php | 4 +- src/PhpGenerator/PropertyAccessMode.php | 19 +---- src/PhpGenerator/PropertyHookType.php | 19 +---- src/PhpGenerator/Traits/PropertyLike.php | 78 ++++++++++----------- src/PhpGenerator/Traits/VisibilityAware.php | 12 ++-- src/PhpGenerator/Visibility.php | 21 ++---- 7 files changed, 57 insertions(+), 100 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 761777d7..89eb7e54 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -548,7 +548,7 @@ private function toValue(Node\Expr $node): mixed } - private function toVisibility(int $flags): ?string + private function toVisibility(int $flags): ?Visibility { return match (true) { (bool) ($flags & Modifiers::PUBLIC) => Visibility::Public, @@ -559,7 +559,7 @@ private function toVisibility(int $flags): ?string } - private function toSetterVisibility(int $flags): ?string + private function toSetterVisibility(int $flags): ?Visibility { return match (true) { !class_exists(Node\PropertyHook::class) => null, diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 7e4f125f..2f164b17 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -117,7 +117,7 @@ public function fromClassReflection( } $modifier = $declaringMethod->getModifiers() !== $method->getModifiers() - ? ' ' . $this->getVisibility($method) + ? ' ' . $this->getVisibility($method)->value : null; $alias = $declaringMethod->name !== $method->name ? ' ' . $method->name : ''; if ($modifier || $alias) { @@ -361,7 +361,7 @@ private function getAttributes($from): array } - private function getVisibility(\ReflectionProperty|\ReflectionMethod|\ReflectionClassConstant $from): string + private function getVisibility(\ReflectionProperty|\ReflectionMethod|\ReflectionClassConstant $from): Visibility { return $from->isPrivate() ? Visibility::Private diff --git a/src/PhpGenerator/PropertyAccessMode.php b/src/PhpGenerator/PropertyAccessMode.php index ac53aeb8..54271b2f 100644 --- a/src/PhpGenerator/PropertyAccessMode.php +++ b/src/PhpGenerator/PropertyAccessMode.php @@ -9,25 +9,12 @@ namespace Nette\PhpGenerator; -use Nette; - /** * Property access mode. */ -/*enum*/ final class PropertyAccessMode +enum PropertyAccessMode: string { - use Nette\StaticClass; - - public const Set = 'set'; - public const Get = 'get'; - - - /** @internal */ - public static function from(string $value): string - { - return $value === self::Set || $value === self::Get - ? $value - : throw new \ValueError("'$value' is not a valid value of access mode"); - } + case Set = 'set'; + case Get = 'get'; } diff --git a/src/PhpGenerator/PropertyHookType.php b/src/PhpGenerator/PropertyHookType.php index ca23532f..f9224a5d 100644 --- a/src/PhpGenerator/PropertyHookType.php +++ b/src/PhpGenerator/PropertyHookType.php @@ -9,25 +9,12 @@ namespace Nette\PhpGenerator; -use Nette; - /** * Property hook type. */ -/*enum*/ final class PropertyHookType +enum PropertyHookType: string { - use Nette\StaticClass; - - public const Set = 'set'; - public const Get = 'get'; - - - /** @internal */ - public static function from(string $value): string - { - return $value === self::Set || $value === self::Get - ? $value - : throw new \ValueError("'$value' is not a valid value of hook type"); - } + case Set = 'set'; + case Get = 'get'; } diff --git a/src/PhpGenerator/Traits/PropertyLike.php b/src/PhpGenerator/Traits/PropertyLike.php index 7ed4b9d9..e7997518 100644 --- a/src/PhpGenerator/Traits/PropertyLike.php +++ b/src/PhpGenerator/Traits/PropertyLike.php @@ -13,7 +13,7 @@ use Nette\PhpGenerator\PropertyHook; use Nette\PhpGenerator\PropertyHookType; use Nette\PhpGenerator\Visibility; -use function array_filter, in_array; +use function array_filter, in_array, is_string; /** @@ -21,78 +21,74 @@ */ trait PropertyLike { - /** @var array{set: ?string, get: ?string} */ - private array $visibility = [PropertyAccessMode::Set => null, PropertyAccessMode::Get => null]; + /** @var array{set: ?Visibility, get: ?Visibility} */ + private array $visibility = ['set' => null, 'get' => null]; private bool $final = false; private bool $readOnly = false; /** @var array */ - private array $hooks = [PropertyHookType::Set => null, PropertyHookType::Get => null]; + private array $hooks = ['set' => null, 'get' => null]; - /** - * @param 'public'|'protected'|'private'|null $get - * @param 'public'|'protected'|'private'|null $set - */ - public function setVisibility(?string $get, ?string $set = null): static + public function setVisibility(Visibility|string|null $get, Visibility|string|null $set = null): static { $this->visibility = [ - PropertyAccessMode::Set => $set === null ? $set : Visibility::from($set), - PropertyAccessMode::Get => $get === null ? $get : Visibility::from($get), + 'set' => $set instanceof Visibility || $set === null ? $set : Visibility::from($set), + 'get' => $get instanceof Visibility || $get === null ? $get : Visibility::from($get), ]; return $this; } - /** @param 'set'|'get' $mode */ - public function getVisibility(string $mode = PropertyAccessMode::Get): ?string + public function getVisibility(PropertyAccessMode|string $mode = PropertyAccessMode::Get): ?string { - return $this->visibility[PropertyAccessMode::from($mode)]; + $mode = is_string($mode) ? PropertyAccessMode::from($mode) : $mode; + return $this->visibility[$mode->value]?->value; } - /** @param 'set'|'get' $mode */ - public function setPublic(string $mode = PropertyAccessMode::Get): static + public function setPublic(PropertyAccessMode|string $mode = PropertyAccessMode::Get): static { - $this->visibility[PropertyAccessMode::from($mode)] = Visibility::Public; + $mode = is_string($mode) ? PropertyAccessMode::from($mode) : $mode; + $this->visibility[$mode->value] = Visibility::Public; return $this; } - /** @param 'set'|'get' $mode */ - public function isPublic(string $mode = PropertyAccessMode::Get): bool + public function isPublic(PropertyAccessMode|string $mode = PropertyAccessMode::Get): bool { - return in_array($this->visibility[PropertyAccessMode::from($mode)], [Visibility::Public, null], true); + $mode = is_string($mode) ? PropertyAccessMode::from($mode) : $mode; + return in_array($this->visibility[$mode->value], [Visibility::Public, null], true); } - /** @param 'set'|'get' $mode */ - public function setProtected(string $mode = PropertyAccessMode::Get): static + public function setProtected(PropertyAccessMode|string $mode = PropertyAccessMode::Get): static { - $this->visibility[PropertyAccessMode::from($mode)] = Visibility::Protected; + $mode = is_string($mode) ? PropertyAccessMode::from($mode) : $mode; + $this->visibility[$mode->value] = Visibility::Protected; return $this; } - /** @param 'set'|'get' $mode */ - public function isProtected(string $mode = PropertyAccessMode::Get): bool + public function isProtected(PropertyAccessMode|string $mode = PropertyAccessMode::Get): bool { - return $this->visibility[PropertyAccessMode::from($mode)] === Visibility::Protected; + $mode = is_string($mode) ? PropertyAccessMode::from($mode) : $mode; + return $this->visibility[$mode->value] === Visibility::Protected; } - /** @param 'set'|'get' $mode */ - public function setPrivate(string $mode = PropertyAccessMode::Get): static + public function setPrivate(PropertyAccessMode|string $mode = PropertyAccessMode::Get): static { - $this->visibility[PropertyAccessMode::from($mode)] = Visibility::Private; + $mode = is_string($mode) ? PropertyAccessMode::from($mode) : $mode; + $this->visibility[$mode->value] = Visibility::Private; return $this; } - /** @param 'set'|'get' $mode */ - public function isPrivate(string $mode = PropertyAccessMode::Get): bool + public function isPrivate(PropertyAccessMode|string $mode = PropertyAccessMode::Get): bool { - return $this->visibility[PropertyAccessMode::from($mode)] === Visibility::Private; + $mode = is_string($mode) ? PropertyAccessMode::from($mode) : $mode; + return $this->visibility[$mode->value] === Visibility::Private; } @@ -141,24 +137,24 @@ public function getHooks(): array } - /** @param 'set'|'get' $type */ - public function addHook(string $type, string $shortBody = ''): PropertyHook + public function addHook(PropertyHookType|string $type, string $shortBody = ''): PropertyHook { - return $this->hooks[PropertyHookType::from($type)] = (new PropertyHook) + $type = is_string($type) ? PropertyHookType::from($type) : $type; + return $this->hooks[$type->value] = (new PropertyHook) ->setBody($shortBody, short: true); } - /** @param 'set'|'get' $type */ - public function getHook(string $type): ?PropertyHook + public function getHook(PropertyHookType|string $type): ?PropertyHook { - return $this->hooks[PropertyHookType::from($type)] ?? null; + $type = is_string($type) ? PropertyHookType::from($type) : $type; + return $this->hooks[$type->value] ?? null; } - /** @param 'set'|'get' $type */ - public function hasHook(string $type): bool + public function hasHook(PropertyHookType|string $type): bool { - return isset($this->hooks[PropertyHookType::from($type)]); + $type = is_string($type) ? PropertyHookType::from($type) : $type; + return isset($this->hooks[$type->value]); } } diff --git a/src/PhpGenerator/Traits/VisibilityAware.php b/src/PhpGenerator/Traits/VisibilityAware.php index 0a3a383f..c3da1819 100644 --- a/src/PhpGenerator/Traits/VisibilityAware.php +++ b/src/PhpGenerator/Traits/VisibilityAware.php @@ -17,21 +17,21 @@ */ trait VisibilityAware { - /** public|protected|private */ - private ?string $visibility = null; + private ?Visibility $visibility = null; - /** @param 'public'|'protected'|'private'|null $value */ - public function setVisibility(?string $value): static + public function setVisibility(Visibility|string|null $value): static { - $this->visibility = $value === null ? $value : Visibility::from($value); + $this->visibility = $value instanceof Visibility || $value === null + ? $value + : Visibility::from($value); return $this; } public function getVisibility(): ?string { - return $this->visibility; + return $this->visibility?->value; } diff --git a/src/PhpGenerator/Visibility.php b/src/PhpGenerator/Visibility.php index a31c75c8..34f39cc0 100644 --- a/src/PhpGenerator/Visibility.php +++ b/src/PhpGenerator/Visibility.php @@ -9,26 +9,13 @@ namespace Nette\PhpGenerator; -use Nette; - /** * Member visibility. */ -/*enum*/ final class Visibility +enum Visibility: string { - use Nette\StaticClass; - - public const Public = 'public'; - public const Protected = 'protected'; - public const Private = 'private'; - - - /** @internal */ - public static function from(string $value): string - { - return $value === self::Public || $value === self::Protected || $value === self::Private - ? $value - : throw new \ValueError("'$value' is not a valid value of visibility"); - } + case Public = 'public'; + case Protected = 'protected'; + case Private = 'private'; } From 1be42b2d64928b87c7e02c96f5c0fb7330342df6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 6 Aug 2025 18:36:22 +0200 Subject: [PATCH 266/266] updated phpstan-baseline --- .github/workflows/static-analysis.yml | 2 +- phpstan-baseline.neon | 216 ++++++++++++++++++++++---- 2 files changed, 184 insertions(+), 34 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 396244b2..32145249 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.5 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index dd03b4fa..8a91e267 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,121 +1,271 @@ parameters: ignoreErrors: - - message: '#^Match expression does not handle remaining value\: true$#' + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Attribute \.\.\.\$attrs\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused 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\.$#' + message: '#^Parameter \#1 \$class of method Nette\\PhpGenerator\\Printer\:\:printClass\(\) expects Nette\\PhpGenerator\\ClassType\|Nette\\PhpGenerator\\EnumType\|Nette\\PhpGenerator\\InterfaceType\|Nette\\PhpGenerator\\TraitType, \$this\(Nette\\PhpGenerator\\ClassLike\) given\.$#' + identifier: argument.type + count: 1 + path: src/PhpGenerator/ClassLike.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Constant \.\.\.\$consts\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused + count: 1 + path: src/PhpGenerator/ClassType.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Method \.\.\.\$methods\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused + count: 1 + path: src/PhpGenerator/ClassType.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Property \.\.\.\$props\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused count: 1 path: src/PhpGenerator/ClassType.php - - message: '#^Method Nette\\PhpGenerator\\EnumType\:\:addTrait\(\) has parameter \$deprecatedParam with no value type specified in iterable type array\.$#' + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\TraitUse \.\.\.\$traits\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused + count: 1 + path: src/PhpGenerator/ClassType.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Attribute \.\.\.\$attrs\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused + count: 1 + path: src/PhpGenerator/Closure.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Parameter \.\.\.\$uses\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused + count: 1 + path: src/PhpGenerator/Closure.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Parameter \.\.\.\$val\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused + count: 1 + path: src/PhpGenerator/Closure.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Attribute \.\.\.\$attrs\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused + count: 1 + path: src/PhpGenerator/Constant.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Attribute \.\.\.\$attrs\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused + count: 1 + path: src/PhpGenerator/EnumCase.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Constant \.\.\.\$consts\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused count: 1 path: src/PhpGenerator/EnumType.php - - message: '#^Access to an undefined property PhpParser\\Node\:\:\$attrGroups\.$#' + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\EnumCase \.\.\.\$cases\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused count: 1 - path: src/PhpGenerator/Extractor.php + path: src/PhpGenerator/EnumType.php - - message: '#^Access to an undefined property PhpParser\\Node\\Expr\:\:\$value\.$#' + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Method \.\.\.\$methods\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused count: 1 - path: src/PhpGenerator/Extractor.php + path: src/PhpGenerator/EnumType.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\TraitUse \.\.\.\$traits\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused + count: 1 + path: src/PhpGenerator/EnumType.php - message: '#^Call to an undefined method Nette\\PhpGenerator\\ClassLike\:\:addConstant\(\)\.$#' + identifier: method.notFound count: 1 path: src/PhpGenerator/Extractor.php - message: '#^Call to an undefined method Nette\\PhpGenerator\\ClassLike\:\:addMethod\(\)\.$#' + identifier: method.notFound count: 1 path: src/PhpGenerator/Extractor.php - message: '#^Call to an undefined method Nette\\PhpGenerator\\ClassLike\:\:addProperty\(\)\.$#' + identifier: method.notFound count: 1 path: src/PhpGenerator/Extractor.php - message: '#^Call to an undefined method Nette\\PhpGenerator\\ClassLike\:\:addTrait\(\)\.$#' + identifier: method.notFound count: 1 path: src/PhpGenerator/Extractor.php - - message: '#^Method Nette\\PhpGenerator\\Extractor\:\:addCommentAndAttributes\(\) has parameter \$element with no type specified\.$#' + message: '#^Parameter \#1 \$class of method Nette\\PhpGenerator\\Extractor\:\:addEnumCaseToClass\(\) expects Nette\\PhpGenerator\\EnumType, Nette\\PhpGenerator\\ClassLike given\.$#' + identifier: argument.type count: 1 path: src/PhpGenerator/Extractor.php - - message: '#^Property class@anonymous/PhpGenerator/Extractor\.php\:176\:\:\$callback has no type specified\.$#' + message: '#^Variable \$trait might not be defined\.$#' + identifier: variable.undefined count: 1 path: src/PhpGenerator/Extractor.php - - message: '#^Variable \$trait might not be defined\.$#' - count: 2 - path: src/PhpGenerator/Extractor.php + message: '#^Method Nette\\PhpGenerator\\Factory\:\:getAttributes\(\) has parameter \$from with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: src/PhpGenerator/Factory.php - - message: '#^Call to an undefined method ReflectionClass\\:\:hasCase\(\)\.$#' + message: '#^Variable \$bodies on left side of \?\?\= always exists and is not nullable\.$#' + identifier: nullCoalesce.variable count: 1 path: src/PhpGenerator/Factory.php - - message: '#^Elseif branch is unreachable because previous condition is always true\.$#' + message: '#^Variable \$cache on left side of \?\?\= always exists and is not nullable\.$#' + identifier: nullCoalesce.variable count: 1 path: src/PhpGenerator/Factory.php - - message: '#^Method Nette\\PhpGenerator\\Factory\:\:getAttributes\(\) has parameter \$from with no type specified\.$#' + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Attribute \.\.\.\$attrs\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused count: 1 - path: src/PhpGenerator/Factory.php + path: src/PhpGenerator/GlobalFunction.php - - message: '#^Method Nette\\PhpGenerator\\Factory\:\:getAttributes\(\) return type has no value type specified in iterable type array\.$#' + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Parameter \.\.\.\$val\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused count: 1 - path: src/PhpGenerator/Factory.php + path: src/PhpGenerator/GlobalFunction.php - - message: '#^Method Nette\\PhpGenerator\\Factory\:\:getExtractor\(\) has parameter \$from with no type specified\.$#' + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Constant \.\.\.\$consts\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused count: 1 - path: src/PhpGenerator/Factory.php + path: src/PhpGenerator/InterfaceType.php - - message: '#^Method Nette\\PhpGenerator\\Factory\:\:getVisibility\(\) has parameter \$from with no type specified\.$#' + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Method \.\.\.\$methods\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused count: 1 - path: src/PhpGenerator/Factory.php + path: src/PhpGenerator/InterfaceType.php - - message: '#^Parameter \#1 \$name of method Nette\\PhpGenerator\\ClassType\:\:setExtends\(\) expects string\|null, array\ given\.$#' + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Property \.\.\.\$props\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused count: 1 - path: src/PhpGenerator/Factory.php + path: src/PhpGenerator/InterfaceType.php - - message: '#^Unreachable statement \- code above always terminates\.$#' + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Attribute \.\.\.\$attrs\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused count: 1 - path: src/PhpGenerator/Factory.php + path: src/PhpGenerator/Method.php - - message: '#^Variable \$bodies on left side of \?\?\= always exists and is not nullable\.$#' + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Parameter \.\.\.\$val\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused count: 1 - path: src/PhpGenerator/Factory.php + path: src/PhpGenerator/Method.php - - message: '#^Method Nette\\PhpGenerator\\Helpers\:\:formatArgs\(\) has parameter \$args with no value type specified in iterable type array\.$#' + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Attribute \.\.\.\$attrs\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused count: 1 - path: src/PhpGenerator/Helpers.php + path: src/PhpGenerator/Parameter.php - - message: '#^Method Nette\\PhpGenerator\\Method\:\:from\(\) has parameter \$method with no value type specified in iterable type array\.$#' + message: '#^Instanceof between Nette\\PhpGenerator\\EnumType and Nette\\PhpGenerator\\EnumType will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue count: 1 - path: src/PhpGenerator/Method.php + path: src/PhpGenerator/Printer.php + + - + message: '#^Method Nette\\PhpGenerator\\Printer\:\:printDocComment\(\) has parameter \$commentable with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: src/PhpGenerator/Printer.php + + - + message: '#^Parameter \#2 \$array of function implode expects array\, array\ given\.$#' + identifier: argument.type + count: 2 + path: src/PhpGenerator/Printer.php + + - + message: '#^Result of \|\| is always true\.$#' + identifier: booleanOr.alwaysTrue + count: 1 + path: src/PhpGenerator/Printer.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\PropertyHook \.\.\.\$hooks\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused + count: 1 + path: src/PhpGenerator/PromotedParameter.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Attribute \.\.\.\$attrs\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused + count: 1 + path: src/PhpGenerator/Property.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\PropertyHook \.\.\.\$hooks\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused + count: 1 + path: src/PhpGenerator/Property.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Attribute \.\.\.\$attrs\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused + count: 1 + path: src/PhpGenerator/PropertyHook.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Parameter \.\.\.\$val\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused + count: 1 + path: src/PhpGenerator/PropertyHook.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Constant \.\.\.\$consts\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused + count: 1 + path: src/PhpGenerator/TraitType.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Method \.\.\.\$methods\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused + count: 1 + path: src/PhpGenerator/TraitType.php + + - + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\Property \.\.\.\$props\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused + count: 1 + path: src/PhpGenerator/TraitType.php - - message: '#^Method Nette\\PhpGenerator\\TraitType\:\:addTrait\(\) has parameter \$deprecatedParam with no value type specified in iterable type array\.$#' + message: '#^Expression "\(function \(\\Nette\\PhpGenerator\\TraitUse \.\.\.\$traits\) \{…" on a separate line does not do anything\.$#' + identifier: expr.resultUnused count: 1 path: src/PhpGenerator/TraitType.php