diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
index 4b5bf98ee..31d64e600 100644
--- a/.php-cs-fixer.dist.php
+++ b/.php-cs-fixer.dist.php
@@ -13,6 +13,7 @@ $finder = PhpCsFixer\Finder::create();
$finder
->in(__DIR__.'/src')
+ ->in(__DIR__.'/utils')
->append([__FILE__])
->exclude(['docs/', 'tests/app'])
->notPath('tests/app')
diff --git a/composer.json b/composer.json
index f6d3eb27a..d3567bca0 100644
--- a/composer.json
+++ b/composer.json
@@ -67,6 +67,7 @@
"fakerphp/faker": "^1.13",
"jangregor/phpstan-prophecy": "^1.0",
"nelmio/alice": "^3.8",
+ "nikic/php-parser": "^4.15",
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/extension-installer": "^1.2",
"phpstan/phpstan": "^1.9",
@@ -110,7 +111,9 @@
"psr-4": {
"App\\": "tests/app/src/",
"Chill\\DocGeneratorBundle\\Tests\\": "src/Bundle/ChillDocGeneratorBundle/tests",
- "Chill\\WopiBundle\\Tests\\": "src/Bundle/ChillDocGeneratorBundle/tests"
+ "Chill\\WopiBundle\\Tests\\": "src/Bundle/ChillDocGeneratorBundle/tests",
+ "Utils\\Rector\\": "utils/rector/src",
+ "Utils\\Rector\\Tests\\": "utils/rector/tests"
}
},
"config": {
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 56b7c2228..62dbe0468 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -2,6 +2,7 @@ parameters:
level: 5
paths:
- src/
+ - utils/
tmpDir: .cache/
reportUnmatchedIgnoredErrors: false
excludePaths:
diff --git a/phpunit.rector.xml b/phpunit.rector.xml
new file mode 100644
index 000000000..f8d9d3a11
--- /dev/null
+++ b/phpunit.rector.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ utils/rector/tests
+
+
+
+
+
+ utils/rector/src
+
+
+
diff --git a/utils/rector/src/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector.php b/utils/rector/src/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector.php
new file mode 100644
index 000000000..c41ecfa91
--- /dev/null
+++ b/utils/rector/src/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector.php
@@ -0,0 +1,170 @@
+classAnalyzer->hasImplements($node, FilterInterface::class)) {
+ return null;
+ }
+
+ $buildFormStmtIndex = null;
+ $hasGetFormDefaultDataMethod = false;
+ foreach ($node->stmts as $k => $stmt) {
+ if (!$stmt instanceof Node\Stmt\ClassMethod) {
+ continue;
+ }
+
+ if ('buildForm' === $stmt->name->name) {
+ $buildFormStmtIndex = $k;
+ }
+
+ if ('getFormDefaultData' === $stmt->name->name) {
+ $hasGetFormDefaultDataMethod = true;
+ }
+ }
+
+ if ($hasGetFormDefaultDataMethod || null === $buildFormStmtIndex) {
+ return null;
+ }
+
+ $stmtBefore = array_slice($node->stmts, 0, $buildFormStmtIndex, false);
+ $stmtAfter = array_slice($node->stmts, $buildFormStmtIndex + 1);
+
+ // lines to satisfay phpstan parser
+ if (!$node->stmts[$buildFormStmtIndex] instanceof Node\Stmt\ClassMethod) {
+ throw new \LogicException();
+ }
+
+ ['build_form_method' => $buildFormMethod, 'empty_to_replace' => $emptyToReplace]
+ = $this->filterBuildFormMethod($node->stmts[$buildFormStmtIndex]);
+
+ $node->stmts = [
+ ...$stmtBefore,
+ $buildFormMethod,
+ $this->makeGetFormDefaultData($node->stmts[$buildFormStmtIndex], $emptyToReplace),
+ ...$stmtAfter,
+ ];
+
+ return $node;
+ }
+
+ private function makeGetFormDefaultData(Node\Stmt\ClassMethod $buildFormMethod, array $emptyToReplace): Node\Stmt\ClassMethod
+ {
+ $method = new Node\Stmt\ClassMethod('getFormDefaultData');
+ $method->flags = Node\Stmt\Class_::MODIFIER_PUBLIC;
+ $method->returnType = new Node\Identifier('array');
+
+ $data = new Node\Expr\Array_([]);
+
+ foreach ($emptyToReplace as $key => $value) {
+ $item = new Node\Expr\ArrayItem($value, new Node\Scalar\String_($key));
+ $data->items[] = $item;
+ }
+
+ $method->stmts[] = new Node\Stmt\Return_($data);
+
+ return $method;
+ }
+
+ /**
+ * @param Node\Stmt\ClassMethod $buildFormMethod
+ * @return array{"build_form_method": Node\Stmt\ClassMethod, "empty_to_replace": array}
+ */
+ private function filterBuildFormMethod(Node\Stmt\ClassMethod $buildFormMethod): array
+ {
+ $builderName = $buildFormMethod->params[0]->var->name;
+
+ $newStmts = [];
+ $emptyDataToReplace = [];
+
+ foreach ($buildFormMethod->stmts as $stmt) {
+ if ($stmt instanceof Node\Stmt\Expression
+ // it must be a method call
+ && $stmt->expr instanceof Node\Expr\MethodCall
+ // the method call must be "add"
+ && $stmt->expr->name instanceof Node\Identifier
+ && $stmt->expr->name->name === 'add'
+ // and the method call must apply on the builder (compare with builderName)
+ && $stmt->expr->var instanceof Node\Expr\Variable
+ && $stmt->expr->var->name === $builderName
+ // it must have a first argument, a string
+ // TODO what happens if a value, or a const ?
+ && ($stmt->expr->args[0] ?? null) instanceof Node\Arg
+ && $stmt->expr->args[0]->value instanceof Node\Scalar\String_
+ // and a third argument, an array
+ && ($stmt->expr->args[2] ?? null) instanceof Node\Arg
+ && $stmt->expr->args[2]->value instanceof Node\Expr\Array_
+ ) {
+
+ // we parse on the 3rd argument, to find if there is an 'empty_data' key
+ $emptyDataIndex = null;
+ foreach ($stmt->expr->args[2]->value->items as $arrayItemIndex => $item) {
+ /* @phpstan-ignore-next-line */
+ if ($item->key->value === 'data') {
+ $k = $stmt->expr->args[0]->value->value;
+ $emptyDataToReplace[$k] = $item->value;
+ $emptyDataIndex = $arrayItemIndex;
+ }
+ }
+
+ if (null !== $emptyDataIndex) {
+ $stmt->expr->args[2]->value->items = array_values(
+ array_filter(
+ $stmt->expr->args[2]->value->items,
+ /* @phpstan-ignore-next-line */
+ fn (Node\Expr\ArrayItem $item) => $item->key->value !== 'data'
+ )
+ );
+ }
+
+ $newStmts[] = $stmt;
+ } else {
+ $newStmts[] = $stmt;
+ }
+ }
+
+ $buildFormMethod->stmts = $newStmts;
+
+ return ['build_form_method' => $buildFormMethod, "empty_to_replace" => $emptyDataToReplace];
+ }
+}
diff --git a/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRectorTest.php b/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRectorTest.php
new file mode 100644
index 000000000..800d2876f
--- /dev/null
+++ b/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRectorTest.php
@@ -0,0 +1,40 @@
+doTestFile($file);
+ }
+
+ public function provideData(): \Iterator
+ {
+ return self::yieldFilesFromDirectory(__DIR__.'/Fixture');
+ }
+
+ public function provideConfigFilePath(): string
+ {
+ return __DIR__.'/config/config.php';
+ }
+}
diff --git a/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data.php.inc b/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data.php.inc
new file mode 100644
index 000000000..5429d3c82
--- /dev/null
+++ b/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data.php.inc
@@ -0,0 +1,107 @@
+add('foo', PickRollingDateType::class, [
+ 'label' => 'Test thing',
+ 'data' => new RollingDate(RollingDate::T_TODAY)
+ ]);
+
+ $builder->add('baz', TextType::class, [
+ 'label' => 'OrNiCar',
+ 'data' => 'Castor'
+ ]);
+ }
+
+ public function getTitle()
+ {
+ // TODO: Implement getTitle() method.
+ }
+
+ public function addRole(): ?string
+ {
+ // TODO: Implement addRole() method.
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ // TODO: Implement alterQuery() method.
+ }
+
+ public function applyOn()
+ {
+ // TODO: Implement applyOn() method.
+ }
+}
+?>
+-----
+add('foo', PickRollingDateType::class, [
+ 'label' => 'Test thing'
+ ]);
+
+ $builder->add('baz', TextType::class, [
+ 'label' => 'OrNiCar'
+ ]);
+ }
+ public function getFormDefaultData(): array
+ {
+ return ['foo' => new RollingDate(RollingDate::T_TODAY), 'baz' => 'Castor'];
+ }
+
+ public function getTitle()
+ {
+ // TODO: Implement getTitle() method.
+ }
+
+ public function addRole(): ?string
+ {
+ // TODO: Implement addRole() method.
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ // TODO: Implement alterQuery() method.
+ }
+
+ public function applyOn()
+ {
+ // TODO: Implement applyOn() method.
+ }
+}
+?>
diff --git a/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-no-data-on-builder.php.inc b/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-no-data-on-builder.php.inc
new file mode 100644
index 000000000..285c16b50
--- /dev/null
+++ b/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-no-data-on-builder.php.inc
@@ -0,0 +1,105 @@
+add('foo', PickRollingDateType::class, [
+ 'label' => 'Test thing',
+ ]);
+
+ $builder->add('baz', TextType::class, [
+ 'label' => 'OrNiCar',
+ ]);
+ }
+
+ public function getTitle()
+ {
+ // TODO: Implement getTitle() method.
+ }
+
+ public function addRole(): ?string
+ {
+ // TODO: Implement addRole() method.
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ // TODO: Implement alterQuery() method.
+ }
+
+ public function applyOn()
+ {
+ // TODO: Implement applyOn() method.
+ }
+}
+?>
+-----
+add('foo', PickRollingDateType::class, [
+ 'label' => 'Test thing',
+ ]);
+
+ $builder->add('baz', TextType::class, [
+ 'label' => 'OrNiCar',
+ ]);
+ }
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
+
+ public function getTitle()
+ {
+ // TODO: Implement getTitle() method.
+ }
+
+ public function addRole(): ?string
+ {
+ // TODO: Implement addRole() method.
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ // TODO: Implement alterQuery() method.
+ }
+
+ public function applyOn()
+ {
+ // TODO: Implement applyOn() method.
+ }
+}
+?>
diff --git a/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-reuse-data-on-form-default-data.php.inc b/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-reuse-data-on-form-default-data.php.inc
new file mode 100644
index 000000000..b2e78e49c
--- /dev/null
+++ b/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-reuse-data-on-form-default-data.php.inc
@@ -0,0 +1,96 @@
+add('test', PickRollingDateType::class, [
+ 'label' => 'Test thing',
+ 'data' => new RollingDate(RollingDate::T_TODAY)
+ ]);
+ }
+
+ public function getTitle()
+ {
+ // TODO: Implement getTitle() method.
+ }
+
+ public function addRole(): ?string
+ {
+ // TODO: Implement addRole() method.
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ // TODO: Implement alterQuery() method.
+ }
+
+ public function applyOn()
+ {
+ // TODO: Implement applyOn() method.
+ }
+}
+?>
+-----
+add('test', PickRollingDateType::class, [
+ 'label' => 'Test thing'
+ ]);
+ }
+ public function getFormDefaultData(): array
+ {
+ return ['test' => new RollingDate(RollingDate::T_TODAY)];
+ }
+
+ public function getTitle()
+ {
+ // TODO: Implement getTitle() method.
+ }
+
+ public function addRole(): ?string
+ {
+ // TODO: Implement addRole() method.
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ // TODO: Implement alterQuery() method.
+ }
+
+ public function applyOn()
+ {
+ // TODO: Implement applyOn() method.
+ }
+}
+?>
diff --git a/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-with-no-method-get-form-default-data.php.inc b/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-with-no-method-get-form-default-data.php.inc
new file mode 100644
index 000000000..687bd9d0c
--- /dev/null
+++ b/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-with-no-method-get-form-default-data.php.inc
@@ -0,0 +1,87 @@
+
+-----
+
diff --git a/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/skip-filter-existing-get-form-default-data-method.php.inc b/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/skip-filter-existing-get-form-default-data-method.php.inc
new file mode 100644
index 000000000..2fefc908d
--- /dev/null
+++ b/utils/rector/tests/Rector/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/skip-filter-existing-get-form-default-data-method.php.inc
@@ -0,0 +1,46 @@
+rule(\Utils\Rector\Rector\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector::class);
+};