diff --git a/composer.json b/composer.json index 46a80ad..11689d1 100644 --- a/composer.json +++ b/composer.json @@ -47,6 +47,7 @@ "a9f/fractor-extension-installer": "self.version", "a9f/fractor-fluid": "self.version", "a9f/fractor-phpstan-rules": "self.version", + "a9f/fractor-rule-generator": "self.version", "a9f/fractor-typoscript": "self.version", "a9f/fractor-xml": "self.version", "a9f/fractor-yaml": "self.version", @@ -64,6 +65,7 @@ "a9f\\FractorFluid\\": "packages/fractor-fluid/src/", "a9f\\FractorMonorepo\\": "src/", "a9f\\FractorPhpStanRules\\": "packages/fractor-phpstan-rules/src/", + "a9f\\FractorRuleGenerator\\": "packages/fractor-rule-generator/src/", "a9f\\FractorTypoScript\\": "packages/fractor-typoscript/src/", "a9f\\FractorXml\\": "packages/fractor-xml/src/", "a9f\\FractorYaml\\": "packages/fractor-yaml/src/", @@ -83,6 +85,7 @@ "a9f\\FractorDocGenerator\\Tests\\": "packages/fractor-doc-generator/tests/", "a9f\\FractorFluid\\Tests\\": "packages/fractor-fluid/tests/", "a9f\\FractorPhpStanRules\\Tests\\": "packages/fractor-phpstan-rules/tests/", + "a9f\\FractorRuleGenerator\\Tests\\": "packages/fractor-rule-generator/tests/", "a9f\\FractorTypoScript\\Tests\\": "packages/fractor-typoscript/tests/", "a9f\\FractorXml\\Tests\\": "packages/fractor-xml/tests/", "a9f\\FractorYaml\\Tests\\": "packages/fractor-yaml/tests/", diff --git a/ecs.php b/ecs.php index 0b932a3..7d75b4b 100644 --- a/ecs.php +++ b/ecs.php @@ -31,7 +31,10 @@ YodaStyleFixer::class, OperatorLinebreakFixer::class, ]) - ->withSkip([__DIR__ . '/packages/extension-installer/generated']) + ->withSkip([ + __DIR__ . '/packages/extension-installer/generated', + __DIR__ . '/packages/fractor-rule-generator/templates', + ]) ->withPreparedSets(psr12: true, common: true, symplify: true, cleanCode: true) ->withPaths([__DIR__ . '/e2e', __DIR__ . '/src', __DIR__ . '/packages']) ->withRootFiles(); diff --git a/packages/fractor-rule-generator/.gitignore b/packages/fractor-rule-generator/.gitignore new file mode 100644 index 0000000..a89e45e --- /dev/null +++ b/packages/fractor-rule-generator/.gitignore @@ -0,0 +1,3 @@ +/vendor/ +/composer.lock +.phpunit.cache \ No newline at end of file diff --git a/packages/fractor-rule-generator/composer.json b/packages/fractor-rule-generator/composer.json new file mode 100644 index 0000000..9339629 --- /dev/null +++ b/packages/fractor-rule-generator/composer.json @@ -0,0 +1,49 @@ +{ + "name": "a9f/fractor-rule-generator", + "description": "Fractor rule generator", + "license": "MIT", + "type": "fractor-extension", + "authors": [ + { + "name": "Andreas Wolf", + "email": "dev@a-w.io", + "role": "Lead Developer" + } + ], + "require": { + "php": "^8.2", + "a9f/fractor": "^0.3", + "a9f/fractor-extension-installer": "^0.3", + "nette/utils": "^4.0", + "symfony/config": "^5.4 || ^6.4 || ^7.0", + "symfony/console": "^5.4 || ^6.4 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.4 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.4 || ^7.0", + "symfony/finder": "^5.4 || ^6.4 || ^7.0", + "symplify/rule-doc-generator-contracts": "^11.2", + "webmozart/assert": "^1.11" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "autoload": { + "psr-4": { + "a9f\\FractorRuleGenerator\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "a9f\\FractorRuleGenerator\\Tests\\": "tests/" + } + }, + "config": { + "allow-plugins": { + "a9f/fractor-extension-installer": true + }, + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-main": "0.3-dev" + } + } +} diff --git a/packages/fractor-rule-generator/config/application.php b/packages/fractor-rule-generator/config/application.php new file mode 100644 index 0000000..7f88895 --- /dev/null +++ b/packages/fractor-rule-generator/config/application.php @@ -0,0 +1,22 @@ +services(); + $services->defaults() + ->autowire() + ->private() + ->autoconfigure(); + + $services->load('a9f\\FractorRuleGenerator\\', __DIR__ . '/../../fractor-rule-generator/src') + ->exclude([__DIR__ . '/../src/ValueObject', __DIR__ . '/../src/**/ValueObject']); + + $services->set(ConsoleOutput::class); + $services->alias(OutputInterface::class, ConsoleOutput::class); +}; diff --git a/packages/fractor-rule-generator/phpunit.xml b/packages/fractor-rule-generator/phpunit.xml new file mode 100644 index 0000000..30100d0 --- /dev/null +++ b/packages/fractor-rule-generator/phpunit.xml @@ -0,0 +1,13 @@ + + + + + tests + + + + + ./src + + + diff --git a/packages/fractor-rule-generator/src/Console/Command/GenerateRuleCommand.php b/packages/fractor-rule-generator/src/Console/Command/GenerateRuleCommand.php new file mode 100644 index 0000000..cdc059f --- /dev/null +++ b/packages/fractor-rule-generator/src/Console/Command/GenerateRuleCommand.php @@ -0,0 +1,242 @@ +getHelper('question'); + + /** @var Typo3Version $typo3Version */ + $typo3Version = $helper->ask($input, $output, $this->askForTypo3Version()); + $changelogUrl = $helper->ask($input, $output, $this->askForChangelogUrl()); + $name = $helper->ask($input, $output, $this->askForName()); + $description = $helper->ask($input, $output, $this->askForDescription()); + $type = $helper->ask($input, $output, $this->askForType()); + + $recipe = new Typo3FractorRecipe( + $typo3Version, + $changelogUrl, + $name, + $description, + Typo3FractorTypeFactory::fromString($type) + ); + + $templateFileInfos = $this->templateFinder->find($recipe->getFractorFixtureFileExtension()); + + $templateVariables = [ + '__MajorPrefixed__' => $recipe->getMajorVersionPrefixed(), + '__Major__' => $recipe->getMajorVersion(), + '__MinorPrefixed__' => $recipe->getMinorVersionPrefixed(), + '__Type__' => $recipe->getFractorTypeFolderName(), + '__FixtureFileExtension__' => $recipe->getFractorFixtureFileExtension(), + '__Name__' => $recipe->getFractorName(), + '__Test_Directory__' => $recipe->getTestDirectory(), + '__Changelog_Annotation__' => $recipe->getChangelogAnnotation(), + '__Description__' => addslashes($recipe->getDescription()), + '__Use__' => $recipe->getUseImports(), + '__Traits__' => $recipe->getTraits(), + '__ExtendsImplements__' => $recipe->getExtendsImplements(), + '__Base_Fractor_Body_Template__' => $recipe->getFractorBodyTemplate(), + ]; + + $targetDirectory = __DIR__ . '/../../../../typo3-fractor'; + + $generatedFilePaths = $this->fileGenerator->generateFiles( + $templateFileInfos, + $templateVariables, + $targetDirectory + ); + + $this->configFilesystem->addRuleToConfigurationFile( + $recipe->getSet(), + $templateVariables, + self::FRACTOR_FQN_NAME_PATTERN + ); + + $testCaseDirectoryPath = $this->resolveTestCaseDirectoryPath($generatedFilePaths); + $this->printSuccess($recipe->getFractorName(), $generatedFilePaths, $testCaseDirectoryPath); + + return Command::SUCCESS; + } + + private function askForTypo3Version(): Question + { + $whatTypo3Version = new Question('TYPO3-Version (i.e. 12.0): '); + $whatTypo3Version->setNormalizer( + static fn ($version): Typo3Version => Typo3Version::createFromString(trim((string) $version)) + ); + $whatTypo3Version->setMaxAttempts(2); + $whatTypo3Version->setValidator( + static function (Typo3Version $version): Typo3Version { + Assert::greaterThanEq($version->getMajor(), 7); + Assert::greaterThanEq($version->getMinor(), 0); + + return $version; + } + ); + + return $whatTypo3Version; + } + + private function askForChangelogUrl(): Question + { + $whatIsTheUrlToChangelog = new Question( + 'Url to changelog (i.e. https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/...) or "x" for none: ' + ); + $whatIsTheUrlToChangelog->setMaxAttempts(3); + $whatIsTheUrlToChangelog->setValidator( + static function (?string $url): string { + Assert::notNull($url); + + if (strtolower($url) === 'x') { + return ''; + } + + if (! filter_var($url, FILTER_VALIDATE_URL)) { + throw new RuntimeException('Please enter a valid Url'); + } + + Assert::startsWith($url, 'https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/'); + + return $url; + } + ); + + return $whatIsTheUrlToChangelog; + } + + private function askForName(): Question + { + $giveMeYourName = new Question('Name (i.e MigrateRequiredFlag): '); + $giveMeYourName->setNormalizer( + static fn ($name): ?string => preg_replace('/Fractor$/', '', ucfirst((string) $name)) + ); + $giveMeYourName->setMaxAttempts(3); + $giveMeYourName->setValidator(static function (string $name): string { + Assert::minLength($name, 5); + Assert::maxLength($name, 60); + Assert::notContains($name, ' ', 'The name must not contain spaces'); + // Pattern from: https://www.php.net/manual/en/language.oop5.basic.php + Assert::regex( + $name, + '/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', + 'The name must be a valid PHP class name. A valid class name starts with a letter or underscore, followed by any number of letters, numbers, or underscores.' + ); + + return $name; + }); + + return $giveMeYourName; + } + + private function askForDescription(): Question + { + $description = new Question('Description (i.e. Migrate required flag): '); + $description->setMaxAttempts(3); + $description->setValidator(static function (?string $description): string { + Assert::notNull($description, 'Please enter a description'); + Assert::minLength($description, 5); + Assert::maxLength($description, 120); + + return $description; + }); + + return $description; + } + + private function askForType(): Question + { + $question = new ChoiceQuestion('Please select the Fractor type', [ + 'flexform', + 'fluid', + 'typoscript', + 'yaml', + 'composer', + ]); + $question->setMaxAttempts(3); + $question->setErrorMessage('Type %s is invalid.'); + + return $question; + } + + /** + * @param string[] $generatedFilePaths + */ + private function printSuccess(string $name, array $generatedFilePaths, string $testCaseFilePath): void + { + $message = sprintf('New files generated for "%s":', $name); + $this->outputStyle->writeln($message); + + sort($generatedFilePaths); + + foreach ($generatedFilePaths as $generatedFilePath) { + $fileInfo = $this->fileInfoFactory->createFileInfoFromPath($generatedFilePath); + $this->outputStyle->writeln(' * ' . $fileInfo->getRelativePathname()); + } + + $message = sprintf( + 'Run tests for this fractor:%svendor/bin/phpunit %s', + PHP_EOL . PHP_EOL, + $testCaseFilePath . PHP_EOL + ); + $this->outputStyle->writeln($message); + } + + /** + * @param string[] $generatedFilePaths + */ + private function resolveTestCaseDirectoryPath(array $generatedFilePaths): string + { + foreach ($generatedFilePaths as $generatedFilePath) { + if (! \str_ends_with($generatedFilePath, 'Test.php') + && ! \str_ends_with($generatedFilePath, 'Test.php.inc') + ) { + continue; + } + + $generatedFileInfo = $this->fileInfoFactory->createFileInfoFromPath($generatedFilePath); + return $generatedFileInfo->getRelativePath(); + } + + throw new ShouldNotHappenException(); + } +} diff --git a/packages/fractor-rule-generator/src/Contract/Typo3FractorTypeInterface.php b/packages/fractor-rule-generator/src/Contract/Typo3FractorTypeInterface.php new file mode 100644 index 0000000..b691a71 --- /dev/null +++ b/packages/fractor-rule-generator/src/Contract/Typo3FractorTypeInterface.php @@ -0,0 +1,20 @@ + $variables + */ + public function create(string $content, array $variables): string + { + $variableKeys = array_keys($variables); + $variableValues = array_values($variables); + + return str_replace($variableKeys, $variableValues, $content); + } +} diff --git a/packages/fractor-rule-generator/src/Factory/Typo3FractorTypeFactory.php b/packages/fractor-rule-generator/src/Factory/Typo3FractorTypeFactory.php new file mode 100644 index 0000000..d501132 --- /dev/null +++ b/packages/fractor-rule-generator/src/Factory/Typo3FractorTypeFactory.php @@ -0,0 +1,27 @@ + new ComposerJsonFractorType(), + 'flexform' => new FlexFormFractorType(), + 'fluid' => new FluidFractorType(), + 'typoscript' => new TypoScriptFractorType(), + 'yaml' => new YamlFractorType(), + default => throw new \Exception('Invalid type given'), + }; + } +} diff --git a/packages/fractor-rule-generator/src/FileSystem/ConfigFilesystem.php b/packages/fractor-rule-generator/src/FileSystem/ConfigFilesystem.php new file mode 100644 index 0000000..8ddaf54 --- /dev/null +++ b/packages/fractor-rule-generator/src/FileSystem/ConfigFilesystem.php @@ -0,0 +1,95 @@ + $templateVariables + */ + public function addRuleToConfigurationFile( + string $configFilePath, + array $templateVariables, + string $rectorFqnNamePattern + ): void { + $this->createConfigurationFileIfNotExists($configFilePath); + + $configFileContents = (string) file_get_contents($configFilePath); + + $this->ensureRequiredKeysAreSet($templateVariables); + + // already added? + $servicesFullyQualifiedName = $this->templateFactory->create($rectorFqnNamePattern, $templateVariables); + if (\str_contains($configFileContents, $servicesFullyQualifiedName)) { + return; + } + + $rule = sprintf('$services->set(\\%s::class);', $servicesFullyQualifiedName); + // Add new rule to existing ones or add as first rule of new configuration file. + if (Strings::match($configFileContents, self::LAST_ITEM_REGEX) !== null + && Strings::match($configFileContents, self::LAST_ITEM_REGEX) !== [] + ) { + $registerServiceLine = sprintf(';' . PHP_EOL . ' %s' . PHP_EOL . '};', $rule); + $configFileContents = Strings::replace($configFileContents, self::LAST_ITEM_REGEX, $registerServiceLine); + } else { + $configFileContents = str_replace('###FIRST_RULE###', $rule, $configFileContents); + } + + // Print the content back to file + $this->filesystem->dumpFile($configFilePath, $configFileContents); + } + + /** + * @param array $templateVariables + */ + private function ensureRequiredKeysAreSet(array $templateVariables): void + { + $missingKeys = array_diff(self::REQUIRED_KEYS, array_keys($templateVariables)); + if ($missingKeys === []) { + return; + } + + $message = sprintf('Template variables for "%s" keys are missing', implode('", "', $missingKeys)); + throw new ShouldNotHappenException($message); + } + + private function createConfigurationFileIfNotExists(string $configFilePath): void + { + if ($this->filesystem->exists($configFilePath)) { + return; + } + + $parentDirectory = dirname($configFilePath); + $this->filesystem->mkdir($parentDirectory); + $this->filesystem->touch($configFilePath); + $this->filesystem->appendToFile( + $configFilePath, + (string) file_get_contents(__DIR__ . '/../../templates/config/config.php'), + true + ); + } +} diff --git a/packages/fractor-rule-generator/src/FileSystem/TemplateFileSystem.php b/packages/fractor-rule-generator/src/FileSystem/TemplateFileSystem.php new file mode 100644 index 0000000..25888ed --- /dev/null +++ b/packages/fractor-rule-generator/src/FileSystem/TemplateFileSystem.php @@ -0,0 +1,97 @@ +getRelativeFilePathFromDirectory($smartFileInfo, TemplateFinder::TEMPLATES_DIRECTORY); + $destination = $this->applyVariables($destination, $templateVariables); + + // remove ".inc" protection from PHPUnit if not a test case + if ($this->isNonFixtureFileWithIncSuffix($destination)) { + $destination = Strings::before($destination, '.inc'); + } + + // special hack for tests, to PHPUnit doesn't load the generated file as test case + /** @var string $destination */ + if (\str_ends_with($destination, 'Test.php') && StaticPHPUnitEnvironment::isPHPUnitRun()) { + $destination .= '.inc'; + } + + return $targetDirectory . DIRECTORY_SEPARATOR . $destination; + } + + /** + * @param mixed[] $variables + */ + private function applyVariables(string $content, array $variables): string + { + return str_replace(array_keys($variables), array_values($variables), $content); + } + + private function isNonFixtureFileWithIncSuffix(string $filePath): bool + { + if (Strings::match($filePath, self::FIXTURE_SHORT_REGEX) !== null + && Strings::match($filePath, self::FIXTURE_SHORT_REGEX) !== [] + ) { + return false; + } + + return \str_ends_with($filePath, '.inc'); + } + + private function getRelativeFilePathFromDirectory(SplFileInfo $fileInfo, string $directory): string + { + if (! file_exists($directory)) { + throw new ShouldNotHappenException(sprintf( + 'Directory "%s" was not found in %s.', + $directory, + self::class + )); + } + + $relativeFilePath = $this->filesystem->makePathRelative( + $this->getNormalizedRealPath($fileInfo), + (string) realpath($directory) + ); + return rtrim($relativeFilePath, '/'); + } + + private function getNormalizedRealPath(SplFileInfo $fileInfo): string + { + return $this->normalizePath($fileInfo->getRealPath()); + } + + private function normalizePath(string $path): string + { + return str_replace('\\', '/', $path); + } +} diff --git a/packages/fractor-rule-generator/src/Finder/TemplateFinder.php b/packages/fractor-rule-generator/src/Finder/TemplateFinder.php new file mode 100644 index 0000000..d029710 --- /dev/null +++ b/packages/fractor-rule-generator/src/Finder/TemplateFinder.php @@ -0,0 +1,49 @@ +addRuleAndTestCase($fixtureFileExtension); + + $smartFileInfos = []; + foreach ($filePaths as $filePath) { + $smartFileInfos[] = $this->fileInfoFactory->createFileInfoFromPath($filePath); + } + + return $smartFileInfos; + } + + /** + * @return array + */ + private function addRuleAndTestCase(string $fixtureFileExtension): array + { + return [ + __DIR__ . '/../../templates/rules/TYPO3__MajorPrefixed__/__Type__/__Name__.php', + __DIR__ . '/../../templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/__Name__Test.php.inc', + __DIR__ . '/../../templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/Fixture/fixture.' . $fixtureFileExtension, + __DIR__ . '/../../templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/config/fractor.php.inc', + ]; + } +} diff --git a/packages/fractor-rule-generator/src/Generator/FileGenerator.php b/packages/fractor-rule-generator/src/Generator/FileGenerator.php new file mode 100644 index 0000000..86231c8 --- /dev/null +++ b/packages/fractor-rule-generator/src/Generator/FileGenerator.php @@ -0,0 +1,64 @@ +generateFileInfoWithTemplateVariables( + $fileInfo, + $templateVariables, + $destinationDirectory + ); + } + + return $generatedFilePaths; + } + + /** + * @param array $templateVariables + */ + private function generateFileInfoWithTemplateVariables( + SplFileInfo $smartFileInfo, + array $templateVariables, + string $targetDirectory + ): string { + $targetFilePath = $this->templateFileSystem->resolveDestination( + $smartFileInfo, + $templateVariables, + $targetDirectory + ); + + $content = $this->templateFactory->create($smartFileInfo->getContents(), $templateVariables); + + $this->filesystem->dumpFile($targetFilePath, $content); + + return $targetFilePath; + } +} diff --git a/packages/fractor-rule-generator/src/ValueObject/FractorType/ComposerJsonFractorType.php b/packages/fractor-rule-generator/src/ValueObject/FractorType/ComposerJsonFractorType.php new file mode 100644 index 0000000..bcd67a0 --- /dev/null +++ b/packages/fractor-rule-generator/src/ValueObject/FractorType/ComposerJsonFractorType.php @@ -0,0 +1,56 @@ +url === '') { + return ''; + } + + $url = $this->url; + return <<typo3Version->getMajor()); + } + + public function getMajorVersion(): string + { + return (string) $this->typo3Version->getMajor(); + } + + public function getMinorVersionPrefixed(): string + { + return sprintf('v%d', $this->typo3Version->getMinor()); + } + + public function getDescription(): string + { + return $this->description; + } + + public function getFractorName(): string + { + return $this->name . 'Fractor'; + } + + public function getTestDirectory(): string + { + return $this->name . 'Fractor'; + } + + public function getSet(): string + { + return sprintf(__DIR__ . '/../../../typo3-fractor/config/typo3-%d.php', $this->getMajorVersion()); + } + + public function getUseImports(): string + { + $useImports = ''; + if ($this->url === '') { + $useImports .= <<type->getUseImports(); + } + + public function getTraits(): string + { + return $this->type->getTraits(); + } + + public function getExtendsImplements(): string + { + $extendsImplements = $this->type->getExtendsImplements(); + if ($this->url === '') { + if (str_contains($extendsImplements, 'implements')) { + $extendsImplements .= ', NoChangelogRequired'; + } else { + $extendsImplements .= ' implements NoChangelogRequired'; + } + } + return $extendsImplements; + } + + public function getFractorBodyTemplate(): string + { + return $this->type->getFractorBodyTemplate(); + } + + public function getFractorTypeFolderName(): string + { + return $this->type->getFolderName(); + } + + public function getFractorFixtureFileExtension(): string + { + return $this->type->getFractorFixtureFileExtension(); + } +} diff --git a/packages/fractor-rule-generator/src/ValueObject/Typo3Version.php b/packages/fractor-rule-generator/src/ValueObject/Typo3Version.php new file mode 100644 index 0000000..6457eb0 --- /dev/null +++ b/packages/fractor-rule-generator/src/ValueObject/Typo3Version.php @@ -0,0 +1,40 @@ +major; + } + + public function getMinor(): int + { + return $this->minor; + } + + public static function createFromString(string $version): self + { + if (! str_contains($version, '.')) { + $version .= '.0'; + } + + [$major, $minor] = explode('.', $version, 2); + + return new self((int) $major, (int) $minor); + } + + public function getFullVersion(): string + { + return sprintf('%d%d', $this->major, $this->minor); + } +} diff --git a/packages/fractor-rule-generator/templates/config/config.php b/packages/fractor-rule-generator/templates/config/config.php new file mode 100644 index 0000000..fb2029a --- /dev/null +++ b/packages/fractor-rule-generator/templates/config/config.php @@ -0,0 +1,14 @@ +services(); + $services->defaults() + ->autoconfigure() + ->autowire(); + + ###FIRST_RULE### +}; diff --git a/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/Fixture/fixture.html b/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/Fixture/fixture.html new file mode 100644 index 0000000..31cd93a --- /dev/null +++ b/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/Fixture/fixture.html @@ -0,0 +1,7 @@ +
+ +
+----- +
+ +
diff --git a/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/Fixture/fixture.json b/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/Fixture/fixture.json new file mode 100644 index 0000000..9b7e38c --- /dev/null +++ b/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/Fixture/fixture.json @@ -0,0 +1,7 @@ +{ + "key": "value" +} +----- +{ + "key": "value" +} diff --git a/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/Fixture/fixture.typoscript b/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/Fixture/fixture.typoscript new file mode 100644 index 0000000..1cbe1d2 --- /dev/null +++ b/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/Fixture/fixture.typoscript @@ -0,0 +1,7 @@ +config { + a = 1 +} +----- +config { + a = 1 +} diff --git a/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/Fixture/fixture.xml b/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/Fixture/fixture.xml new file mode 100644 index 0000000..fe07f5b --- /dev/null +++ b/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/Fixture/fixture.xml @@ -0,0 +1,37 @@ + + + + + + sheetTitle + array + + + + input + + + + + + + +----- + + + + + + sheetTitle + array + + + + input + + + + + + + diff --git a/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/Fixture/fixture.yaml b/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/Fixture/fixture.yaml new file mode 100644 index 0000000..367772f --- /dev/null +++ b/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/Fixture/fixture.yaml @@ -0,0 +1,15 @@ +TYPO3: + CMS: + Form: + prototypes: + standard: + formElementsDefinition: + Form: +----- +TYPO3: + CMS: + Form: + prototypes: + standard: + formElementsDefinition: + Form: diff --git a/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/__Name__Test.php.inc b/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/__Name__Test.php.inc new file mode 100644 index 0000000..8b1523f --- /dev/null +++ b/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/__Name__Test.php.inc @@ -0,0 +1,27 @@ +doTestFile($filePath); + } + + public static function provideData(): \Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixtures', '*.__FixtureFileExtension__'); + } + + public function provideConfigFilePath(): ?string + { + return __DIR__ . '/config/fractor.php'; + } +} diff --git a/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/config/fractor.php.inc b/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/config/fractor.php.inc new file mode 100644 index 0000000..a61a94d --- /dev/null +++ b/packages/fractor-rule-generator/templates/rules-tests/TYPO3__MajorPrefixed__/__Type__/__Test_Directory__/config/fractor.php.inc @@ -0,0 +1,15 @@ +withOptions([ + XmlProcessorOption::INDENT_CHARACTER => Indent::STYLE_TAB, + XmlProcessorOption::INDENT_SIZE => 1, + ]) + ->withRules([__Name__::class]); diff --git a/packages/fractor-rule-generator/templates/rules/TYPO3__MajorPrefixed__/__Type__/__Name__.php b/packages/fractor-rule-generator/templates/rules/TYPO3__MajorPrefixed__/__Type__/__Name__.php new file mode 100644 index 0000000..856933a --- /dev/null +++ b/packages/fractor-rule-generator/templates/rules/TYPO3__MajorPrefixed__/__Type__/__Name__.php @@ -0,0 +1,28 @@ +add($createdCommand); + + $foundCommand = $application->find($commandName); + + // Create a CommandTester with your Command class processed by your Application. + $tester = new CommandTester($foundCommand); + + // Respond "y" to the first prompt (question) when the command is invoked. + $tester->setInputs(['7', 'x', 'MigrateTypoScript', 'Migrate TypoScript Setting', '2']); + + // Execute the command. This example would be the equivalent of + // 'bin/console example 127.0.0.1 --ipv6=true' + $tester->execute([ + 'command' => $commandName, + // Arguments as needed. + //'generate-rule' => 'generate-rule', + // Options as needed. + //'--ipv6' => true, + ]); + + //self::assertSame('Example output', $tester->getDisplay()); + self::assertSame(0, $tester->getStatusCode()); + self::assertFileExists(__DIR__ . '/../../../../../typo3-fractor/config/typo3-7.php'); + self::assertFileExists( + __DIR__ . '/../../../../../typo3-fractor/rules/TYPO3v7/TypoScript/MigrateTypoScriptFractor.php' + ); + self::assertFileExists( + __DIR__ . '/../../../../../typo3-fractor/rules-tests/TYPO3v7/TypoScript/MigrateTypoScriptFractor/config/fractor.php' + ); + self::assertFileExists( + __DIR__ . '/../../../../../typo3-fractor/rules-tests/TYPO3v7/TypoScript/MigrateTypoScriptFractor/Fixture/fixture.typoscript' + ); + self::assertFileExists( + __DIR__ . '/../../../../../typo3-fractor/rules-tests/TYPO3v7/TypoScript/MigrateTypoScriptFractor/MigrateTypoScriptFractorTest.php.inc' + ); + + unlink(__DIR__ . '/../../../../../typo3-fractor/config/typo3-7.php'); + unlink(__DIR__ . '/../../../../../typo3-fractor/rules/TYPO3v7/TypoScript/MigrateTypoScriptFractor.php'); + rmdir(__DIR__ . '/../../../../../typo3-fractor/rules/TYPO3v7/TypoScript'); + rmdir(__DIR__ . '/../../../../../typo3-fractor/rules/TYPO3v7'); + + unlink( + __DIR__ . '/../../../../../typo3-fractor/rules-tests/TYPO3v7/TypoScript/MigrateTypoScriptFractor/config/fractor.php' + ); + rmdir(__DIR__ . '/../../../../../typo3-fractor/rules-tests/TYPO3v7/TypoScript/MigrateTypoScriptFractor/config'); + + unlink( + __DIR__ . '/../../../../../typo3-fractor/rules-tests/TYPO3v7/TypoScript/MigrateTypoScriptFractor/Fixture/fixture.typoscript' + ); + rmdir( + __DIR__ . '/../../../../../typo3-fractor/rules-tests/TYPO3v7/TypoScript/MigrateTypoScriptFractor/Fixture' + ); + + unlink( + __DIR__ . '/../../../../../typo3-fractor/rules-tests/TYPO3v7/TypoScript/MigrateTypoScriptFractor/MigrateTypoScriptFractorTest.php.inc' + ); + rmdir(__DIR__ . '/../../../../../typo3-fractor/rules-tests/TYPO3v7/TypoScript/MigrateTypoScriptFractor'); + rmdir(__DIR__ . '/../../../../../typo3-fractor/rules-tests/TYPO3v7/TypoScript'); + rmdir(__DIR__ . '/../../../../../typo3-fractor/rules-tests/TYPO3v7'); + } +} diff --git a/packages/fractor/src/FileSystem/FileInfoFactory.php b/packages/fractor/src/FileSystem/FileInfoFactory.php new file mode 100644 index 0000000..23f8190 --- /dev/null +++ b/packages/fractor/src/FileSystem/FileInfoFactory.php @@ -0,0 +1,36 @@ +filesystem->makePathRelative($realPath, $currentWorkingDirectory), '/'); + $relativeDirectoryPath = dirname($relativeFilePath); + + return new SplFileInfo($filePath, $relativeDirectoryPath, $relativeFilePath); + } +} diff --git a/phpstan.neon b/phpstan.neon index d85e1ea..2f46bc3 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -8,10 +8,10 @@ parameters: paths: - src/ - packages/ - - ecs.php - rector.php excludePaths: - packages/extension-installer/generated - packages/**/tests/**/Fixtures/* - packages/**/tests/**/Fixture/* - packages/**/tests/Fixtures/* + - packages/fractor-rule-generator/templates diff --git a/rector.php b/rector.php index 7d74a38..bd42b7d 100644 --- a/rector.php +++ b/rector.php @@ -8,7 +8,10 @@ ->withPhpSets(php82: true) ->withPreparedSets(deadCode: true, typeDeclarations: true, earlyReturn: true, strictBooleans: true) ->withImportNames(true, true, false, true) - ->withSkip([__DIR__ . '/packages/extension-installer/generated']) + ->withSkip([ + __DIR__ . '/packages/extension-installer/generated', + __DIR__ . '/packages/fractor-rule-generator/templates', + ]) ->withPaths([ __DIR__ . '/ecs.php', __DIR__ . '/packages',