diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 1b43546..d8ad44b 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -44,6 +44,7 @@ jobs: continue-on-error: true - name: Coverage - run: bash <(curl -s https://codecov.io/bash) - env: - PHP_VERSION: ${{ matrix.php }} + uses: codecov/codecov-action@v3 + with: + files: ./coverage.xml + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/composer.json b/composer.json index d198e25..55b3c98 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "twig/twig": "^3.5.0" }, "require-dev": { - "phpunit/phpunit": "^8.5", + "phpunit/phpunit": "^8.5 || ^9.6", "symfony/symfony": "^6.0", "monolog/monolog": "^3.2" }, diff --git a/src/Pug/PugSymfonyEngine.php b/src/Pug/PugSymfonyEngine.php index d174439..8d205ed 100644 --- a/src/Pug/PugSymfonyEngine.php +++ b/src/Pug/PugSymfonyEngine.php @@ -92,10 +92,10 @@ protected function crawlDirectories(string $srcDir, array &$assetsDirectories, a continue; } - if (is_dir($viewDirectory = $srcDir.'/'.$directory.'/Resources/views')) { - if ($baseDir === null) { - $baseDir = $viewDirectory; - } + $viewDirectory = $srcDir.'/'.$directory.'/Resources/views'; + + if (is_dir($viewDirectory)) { + $baseDir ??= $viewDirectory; $viewDirectories[] = $srcDir.'/'.$directory.'/Resources/views'; } diff --git a/src/Pug/Symfony/Traits/HelpersHandler.php b/src/Pug/Symfony/Traits/HelpersHandler.php index 33e3d36..a860aea 100644 --- a/src/Pug/Symfony/Traits/HelpersHandler.php +++ b/src/Pug/Symfony/Traits/HelpersHandler.php @@ -140,7 +140,7 @@ protected function createEngine(array $options): Pug /** @var Closure|null $transformation */ $transformation = $pug->hasOption('patterns') ? ($pug->getOption('patterns')['transform_expression'] ?? null) - : null; + : null; // @codeCoverageIgnore $pug->setOptionsRecursive([ 'patterns' => [ 'transform_expression' => function ($code) use ($transformation) { diff --git a/src/Pug/Symfony/Traits/Installer.php b/src/Pug/Symfony/Traits/Installer.php index ffc086e..bcce897 100644 --- a/src/Pug/Symfony/Traits/Installer.php +++ b/src/Pug/Symfony/Traits/Installer.php @@ -18,6 +18,9 @@ protected static function askConfirmation(IOInterface $io, string $message): boo return !$io->isInteractive() || $io->askConfirmation($message); } + /** + * @SuppressWarnings(PHPMD.ErrorControlOperator) + */ protected static function installSymfonyBundle( IOInterface $io, string $dir, @@ -35,7 +38,7 @@ protected static function installSymfonyBundle( return; } - if (strpos($contents, $bundleClass) !== false) { + if (str_contains($contents, $bundleClass)) { $flags |= InstallerInterface::KERNEL_OK; $io->write('The bundle already exists in config/bundles.php'); @@ -50,7 +53,7 @@ protected static function installSymfonyBundle( file_put_contents($appFile, $contents), InstallerInterface::KERNEL_OK, 'Bundle added to config/bundles.php', - 'Unable to add the bundle engine in config/bundles.php' + 'Unable to add the bundle engine in config/bundles.php', ); } diff --git a/src/Pug/Twig/Environment.php b/src/Pug/Twig/Environment.php index 90a78b3..19b155f 100644 --- a/src/Pug/Twig/Environment.php +++ b/src/Pug/Twig/Environment.php @@ -123,6 +123,9 @@ public function setContainer(ContainerInterface $container): void $this->container = $container; } + /** + * @SuppressWarnings(PHPMD.DuplicatedArrayKey) + */ public function compileSource(Source $source): string { $path = $source->getPath(); @@ -131,7 +134,7 @@ public function compileSource(Source $source): string $pug = $this->getRenderer(); $code = $source->getCode(); $php = $pug->compile($code, $path); - $codeFirstLine = $this->isDebug() ? 31 : 25; + $codeFirstLine = $this->isDebug() ? 39 : 28; $templateLine = 1; $debugInfo = [$codeFirstLine => $templateLine]; $lines = explode("\n", $php); @@ -159,18 +162,20 @@ public function compileSource(Source $source): string $fileName = $this->isDebug() ? 'PugDebugTemplateTemplate' : 'PugTemplateTemplate'; $templateFile = __DIR__."/../../../cache-templates/$fileName.php"; $name = $source->getName(); - $className = isset($this->classNames[$name]) ? $this->classNames[$name] : '__Template_'.sha1($path); + $className = $this->classNames[$name] ?? '__Template_'.sha1($path); + $pathExport = var_export($path, true); $replacements = [ $fileName => $className, - '"{{filename}}"' => var_export($name, true), + "'{{filename}}'" => var_export($name, true), '{{filename}}' => $name, - '"{{path}}"' => var_export($path, true), + "'{{path}}'" => $pathExport, '// {{code}}' => "?>$php var_export($debugInfo, true), + '[/* {{debugInfo}} */]' => var_export(array_reverse($debugInfo, true), true), ]; if ($this->isDebug()) { - $replacements['"{{source}}"'] = var_export($code, true); + $sourceExport = var_export($code, true); + $replacements["'{{source}}'"] = $sourceExport; $replacements['__internal_1'] = '__internal_'.sha1('1'.$path); $replacements['__internal_2'] = '__internal_'.sha1('2'.$path); } @@ -178,9 +183,7 @@ public function compileSource(Source $source): string return strtr(file_get_contents($templateFile), $replacements); } - $html = parent::compileSource($source); - - return $html; + return parent::compileSource($source); } public function loadTemplate(string $cls, string $name, int $index = null): Template diff --git a/tests/Pug/AbstractTestCase.php b/tests/Pug/AbstractTestCase.php index cc2b6d2..b66e288 100644 --- a/tests/Pug/AbstractTestCase.php +++ b/tests/Pug/AbstractTestCase.php @@ -122,7 +122,7 @@ public function setUp(): void $this->addFormRenderer(); } - protected function addFormRenderer() + protected function addFormRenderer(): void { require_once __DIR__.'/TestCsrfTokenManager.php'; diff --git a/tests/Pug/EnvironmentTest.php b/tests/Pug/EnvironmentTest.php index 8877889..98085b3 100644 --- a/tests/Pug/EnvironmentTest.php +++ b/tests/Pug/EnvironmentTest.php @@ -11,7 +11,7 @@ class EnvironmentTest extends AbstractTestCase /** * @throws RuntimeError */ - public function testWithoutRoot() + public function testWithoutRoot(): void { self::expectException(RuntimeError::class); self::expectExceptionMessage('Unable to load the "I-surely-does-not-exist" runtime.'); @@ -23,7 +23,7 @@ public function testWithoutRoot() /** * @throws RuntimeError */ - public function testWithRoot() + public function testWithRoot(): void { self::expectException(RuntimeError::class); self::expectExceptionMessage('Unable to load the "I-surely-does-not-exist" runtime.'); diff --git a/tests/Pug/Exceptions/ReservedVariableTest.php b/tests/Pug/Exceptions/ReservedVariableTest.php index 885836a..2318136 100644 --- a/tests/Pug/Exceptions/ReservedVariableTest.php +++ b/tests/Pug/Exceptions/ReservedVariableTest.php @@ -7,7 +7,7 @@ class ReservedVariableTest extends AbstractTestCase { - public function testConstruct() + public function testConstruct(): void { $exception = new ReservedVariable('foobar'); diff --git a/tests/Pug/InstallerTest.php b/tests/Pug/InstallerTest.php index 0701f49..dfe3823 100644 --- a/tests/Pug/InstallerTest.php +++ b/tests/Pug/InstallerTest.php @@ -17,7 +17,7 @@ class InstallerTest extends AbstractTestCase { - public function testTestInstallQuickExit() + public function testTestInstallQuickExit(): void { $io = new CaptureIO(); $io->setInteractive(false); @@ -35,7 +35,7 @@ public function testTestInstallQuickExit() self::assertSame(['Not inside a composer vendor directory, setup skipped.'], $io->getLastOutput()); } - public function testTestInstall() + public function testTestInstall(): void { $projectDir = sys_get_temp_dir().'/pug-symfony-'.mt_rand(0, 9999999); $fs = new Filesystem(); @@ -72,7 +72,7 @@ public function testTestInstall() self::assertTrue(PugSymfonyEngine::install(new Event('update', new Composer(), $io), $projectDir)); self::assertSame(['Sorry, config/bundles.php has a format we can\'t handle automatically.'], $io->getLastOutput()); - self::assertFileNotExists(__DIR__.'/../../installed'); + self::assertFileDoesNotExist(__DIR__.'/../../installed'); file_put_contents("$projectDir/config/bundles.php", file_get_contents(__DIR__.'/../project-s5/config/bundles-before.php')); $io->reset(); diff --git a/tests/Pug/LogoutUrlHelper.php b/tests/Pug/LogoutUrlHelper.php deleted file mode 100644 index 8000317..0000000 --- a/tests/Pug/LogoutUrlHelper.php +++ /dev/null @@ -1,9 +0,0 @@ -assertStringContainsString('16 templates cached', $output, 'All templates can be cached except filter.pug as the upper filter does not exists.'); + $this->assertStringContainsString('17 templates cached', $output, 'All templates can be cached except filter.pug as the upper filter does not exists.'); $this->assertStringContainsString('1 templates failed to be cached', $output, 'filter.pug fails as the upper filter does not exists.'); - $this->assertRegExp('/(Unknown\sfilter\supper|upper:\sFilter\sdoes\s?n[\'o]t\sexists)/', $output, 'filter.pug fails as the upper filter does not exists.'); + $this->assertMatchesRegularExpression('/(Unknown\sfilter\supper|upper:\sFilter\sdoes\s?n[\'o]t\sexists)/', $output, 'filter.pug fails as the upper filter does not exists.'); $this->assertStringContainsString('filter.pug', $output, 'filter.pug fails as the upper filter does not exists.'); } } diff --git a/tests/Pug/PugSymfonyEngineTest.php b/tests/Pug/PugSymfonyEngineTest.php index 01874e9..c86da98 100644 --- a/tests/Pug/PugSymfonyEngineTest.php +++ b/tests/Pug/PugSymfonyEngineTest.php @@ -14,6 +14,7 @@ use Pug\Symfony\MixedLoader; use Pug\Symfony\Traits\HelpersHandler; use Pug\Symfony\Traits\PrivatePropertyAccessor; +use Pug\Symfony\Traits\PugRenderer; use Pug\Twig\Environment; use ReflectionException; use ReflectionProperty; @@ -25,9 +26,14 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Csrf\CsrfExtension; +use Symfony\Component\Form\Form; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormFactory; use Symfony\Component\Form\FormRegistry; use Symfony\Component\Form\ResolvedFormTypeFactory; +use Symfony\Component\HttpKernel\Attribute\AsController; +use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage as BaseTokenStorage; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Csrf\CsrfTokenManager; @@ -35,6 +41,7 @@ use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator as BaseLogoutUrlGenerator; use Symfony\Component\Translation\Translator; +use Twig\Error\Error; use Twig\Error\LoaderError; use Twig\Loader\ArrayLoader; use Twig\TwigFunction; @@ -96,9 +103,9 @@ public function setDueDate(DateTime $dueDate = null) } } -class TestController +class TestHelper { - public function index() + public static function getFormBuilder(): FormBuilderInterface { $csrfGenerator = new UriSafeTokenGenerator(); $csrfManager = new CsrfTokenManager($csrfGenerator, new class() implements TokenStorageInterface { @@ -125,7 +132,15 @@ public function hasToken(string $tokenId): bool $extensions = [new CsrfExtension($csrfManager)]; $factory = new FormFactory(new FormRegistry($extensions, new ResolvedFormTypeFactory())); - return $factory->createBuilder(FormType::class, new Task()) + return $factory->createBuilder(FormType::class, new Task()); + } +} + +class TestController +{ + public function index() + { + return TestHelper::getFormBuilder() ->add('name', TextType::class) ->add('dueDate', DateType::class) ->add('save', SubmitType::class, ['label' => 'Foo']) @@ -133,11 +148,36 @@ public function hasToken(string $tokenId): bool } } +#[AsController] +class S6Controller +{ + #[Route('/contact')] + public function contactAction(PugSymfonyEngine $pug) + { + return $pug->renderResponse('layout/welcome.pug', [ + 'name' => 'Pug', + ]); + } +} + +class TraitController +{ + use PugRenderer; + + #[Route('/contact')] + public function contactAction() + { + return $this->render('layout/welcome.pug', [ + 'name' => 'Pug', + ]); + } +} + class PugSymfonyEngineTest extends AbstractTestCase { use PrivatePropertyAccessor; - public function testRequireTwig() + public function testRequireTwig(): void { self::expectException(RuntimeException::class); self::expectExceptionMessage('Twig needs to be configured.'); @@ -157,7 +197,7 @@ public function wrongEnhance(): void /** * @throws ErrorException */ - public function testPreRenderPhp() + public function testPreRenderPhp(): void { $kernel = new TestKernel(static function (Container $container) { $container->setParameter('pug', [ @@ -175,7 +215,49 @@ public function testPreRenderPhp() ); } - public function testMixin() + /** + * @throws ErrorException + */ + public function testDebug(): void + { + $kernel = new TestKernel(static function (Container $container) { + $container->setParameter('pug', [ + 'expressionLanguage' => 'php', + ]); + }); + $kernel->boot(); + $pugSymfony = $this->getPugSymfonyEngine(); + $pugSymfony->setOption('prettyprint', false); + $twig = $this->getTwigEnvironment(); + $line = null; + $file = null; + $context = null; + $rawMessage = null; + $errorFile = realpath(__DIR__.'/../project-s5/templates/error.pug'); + + $twig->enableDebug(); + + try { + $twig->render('inc-pug-error.html.twig'); + } catch (Error $error) { + $line = $error->getLine(); + $file = $error->getFile(); + $context = $error->getSourceContext(); + $rawMessage = $error->getRawMessage(); + } + + $twig->disableDebug(); + $code = $context->getCode(); + + self::assertSame(4, $line); + self::assertSame('error.pug', $context->getName()); + self::assertSame(file_get_contents($errorFile), $code); + self::assertSame($errorFile, $context->getPath()); + self::assertSame($errorFile, $file); + self::assertSame('Error', $rawMessage); + } + + public function testMixin(): void { $pugSymfony = $this->getPugSymfonyEngine(); $pugSymfony->setOption('expressionLanguage', 'js'); @@ -190,7 +272,7 @@ public function testMixin() /** * @throws ErrorException */ - public function testPreRenderJs() + public function testPreRenderJs(): void { $kernel = new TestKernel(static function (Container $container) { $container->setParameter('pug', [ @@ -203,7 +285,7 @@ public function testPreRenderJs() self::assertSame('
/foo
', trim($pugSymfony->renderString('p=asset("/foo")'))); } - public function testPreRenderFile() + public function testPreRenderFile(): void { $kernel = new TestKernel(static function (Container $container) { $container->setParameter('pug', [ @@ -227,7 +309,7 @@ public function testPreRenderFile() /** * @throws ErrorException */ - public function testPreRenderCsrfToken() + public function testPreRenderCsrfToken(): void { $kernel = new TestKernel(static function (Container $container) { $container->setParameter('pug', [ @@ -240,10 +322,10 @@ public function testPreRenderCsrfToken() self::assertSame('Hello
', $pugSymfony->renderString('p Hello')); - self::assertRegExp('/[a-zA-Z0-9_-]{20,}<\/p>/', $pugSymfony->renderString('p=csrf_token("authenticate")')); + self::assertMatchesRegularExpression('/
[a-zA-Z0-9_-]{20,}<\/p>/', $pugSymfony->renderString('p=csrf_token("authenticate")')); } - public function testGetEngine() + public function testGetEngine(): void { $pugSymfony = $this->getPugSymfonyEngine(); @@ -251,10 +333,9 @@ public function testGetEngine() } /** - * @throws ErrorException - * @throws ReflectionException + * @throws ErrorException|ReflectionException */ - public function testSecurityToken() + public function testSecurityToken(): void { $tokenStorage = new TokenStorage(); $container = self::$kernel->getContainer(); @@ -269,10 +350,9 @@ public function testSecurityToken() } /** - * @throws ErrorException - * @throws ReflectionException + * @throws ErrorException|ReflectionException */ - public function testLogoutHelper() + public function testLogoutHelper(): void { $generator = new LogoutUrlGenerator(); $twig = $this->getTwigEnvironment(); @@ -294,13 +374,13 @@ public function testLogoutHelper() /** * @throws ErrorException */ - public function testFormHelpers() + public function testFormHelpers(): void { $pugSymfony = $this->getPugSymfonyEngine(); $this->addFormRenderer(); $controller = new TestController(); - self::assertRegExp('/^'.implode('', [ + self::assertMatchesRegularExpression('/^'.implode('', [ '