diff --git a/src/Cache/ComponentsWarmer.php b/src/Cache/ComponentsWarmer.php index 68068c2..1596ba7 100644 --- a/src/Cache/ComponentsWarmer.php +++ b/src/Cache/ComponentsWarmer.php @@ -1,14 +1,19 @@ container->get('twig_doc.service.component'); diff --git a/src/Component/ComponentInvalid.php b/src/Component/ComponentInvalid.php index 740e9eb..3395de4 100644 --- a/src/Component/ComponentInvalid.php +++ b/src/Component/ComponentInvalid.php @@ -9,11 +9,11 @@ /** * @codeCoverageIgnore */ -class ComponentInvalid +readonly class ComponentInvalid { public function __construct( - public readonly ConstraintViolationList $violationList, - public readonly array $originalConfig + public ConstraintViolationList $violationList, + public array $originalConfig ) { } } diff --git a/src/Component/ComponentItem.php b/src/Component/ComponentItem.php index 04fc317..9d529d1 100644 --- a/src/Component/ComponentItem.php +++ b/src/Component/ComponentItem.php @@ -13,21 +13,29 @@ class ComponentItem { #[Assert\NotBlank] private string $name; + #[Assert\NotBlank] private string $title; + #[Assert\NotBlank] private string $description; + #[Assert\Type('array')] private array $tags; + #[Assert\Type('array')] private array $parameters; + #[Assert\Type('array')] private array $variations; + #[Assert\Valid] private ComponentCategory $category; + #[Assert\Length(max: 4096)] #[Assert\NotBlank] private string $projectPath; + #[Assert\Length(max: 4096)] #[Assert\NotBlank] private string $renderPath; diff --git a/src/Component/ComponentItemFactory.php b/src/Component/ComponentItemFactory.php index 146338c..f584db0 100644 --- a/src/Component/ComponentItemFactory.php +++ b/src/Component/ComponentItemFactory.php @@ -9,12 +9,15 @@ use Symfony\Component\Validator\ConstraintViolationList; use Symfony\Component\Validator\Validator\ValidatorInterface; -class ComponentItemFactory +readonly class ComponentItemFactory { - public function __construct(private readonly ValidatorInterface $validator, private readonly CategoryService $categoryService) + public function __construct(private ValidatorInterface $validator, private CategoryService $categoryService) { } + /** + * @throws InvalidComponentConfigurationException + */ public function create(array $data): ComponentItem { $item = $this->createItem($data); @@ -28,7 +31,7 @@ public function create(array $data): ComponentItem isset($data['sub_category']) ? 'sub_category' : 'category', $data['sub_category'] ?? $data['category'], implode(', ', array_keys($this->categoryService->getCategories())), - implode(', ', array_map(fn (ComponentCategory $category) => $category->getName(), $this->categoryService->getSubCategories())) + implode(', ', array_map(static fn (ComponentCategory $category) => $category->getName(), $this->categoryService->getSubCategories())) ) ); throw new InvalidComponentConfigurationException($violations); @@ -68,12 +71,14 @@ public function getParamsFromVariables(array $variables): array foreach ($variables as $dotted) { $keys = explode('.', $dotted); $c = &$r[array_shift($keys)]; + foreach ($keys as $key) { if (isset($c[$key]) && $c[$key] === true) { $c[$key] = []; } $c = &$c[$key]; } + if ($c === null) { $c = 'Scalar'; } diff --git a/src/Component/ComponentItemList.php b/src/Component/ComponentItemList.php index 24de966..72fa073 100644 --- a/src/Component/ComponentItemList.php +++ b/src/Component/ComponentItemList.php @@ -1,5 +1,7 @@ sortableFields)) { + if (!\in_array($field, $this->sortableFields, true)) { throw new \InvalidArgumentException(sprintf('field "%s" is not sortable', $field)); } @@ -52,13 +54,15 @@ public function sort(string $field, string $direction = self::SORT_ASC): void public function filter(string $query, ?string $type): self { $components = []; + switch ($type) { case 'category': $components = array_filter( $this->getArrayCopy(), - function (ComponentItem $item) use ($query) { + static function (ComponentItem $item) use ($query) { $category = $item->getCategory()->getName(); $parent = $item->getCategory()->getParent(); + while ($parent !== null) { $category = $parent->getName(); $parent = $parent->getParent(); @@ -72,14 +76,14 @@ function (ComponentItem $item) use ($query) { case 'sub_category': $components = array_filter( $this->getArrayCopy(), - fn (ComponentItem $item) => $item->getCategory()->getParent() !== null + static fn (ComponentItem $item) => $item->getCategory()->getParent() !== null && strtolower($item->getCategory()->getName()) === strtolower($query) ); break; case 'tags': $tags = array_map('trim', explode(',', strtolower($query))); - $components = array_filter($this->getArrayCopy(), function (ComponentItem $item) use ($tags) { + $components = array_filter($this->getArrayCopy(), static function (ComponentItem $item) use ($tags) { return array_intersect($tags, array_map('strtolower', $item->getTags())) !== []; }); @@ -87,13 +91,13 @@ function (ComponentItem $item) use ($query) { case 'name': $components = array_filter( $this->getArrayCopy(), - fn (ComponentItem $item) => str_contains(strtolower($item->getName()), strtolower($query)) + static fn (ComponentItem $item) => str_contains(strtolower($item->getName()), strtolower($query)) ); break; default: - foreach (['category', 'sub_category', 'tags', 'name'] as $type) { - $components = array_merge($components, (array) $this->filter($query, $type)); + foreach (['category', 'sub_category', 'tags', 'name'] as $componentType) { + $components = array_merge($components, (array) $this->filter($query, $componentType)); } break; diff --git a/src/Configuration/ParserInterface.php b/src/Configuration/ParserInterface.php index 4288eff..a454a58 100644 --- a/src/Configuration/ParserInterface.php +++ b/src/Configuration/ParserInterface.php @@ -1,5 +1,7 @@ valid()) { $line = $fileObject->current(); + if (empty(trim($line))) { $fileObject->next(); continue; } + if ($firstLineDetected === false) { $firstLineDetected = true; // check for whitespaces at the beginning @@ -39,6 +42,7 @@ private function fixIndentation(string $content): string } $indentationWhitespace = $matches[1]; } + $line = substr($line, \strlen($indentationWhitespace)); $lines[] = $line; $fileObject->next(); diff --git a/src/Controller/TwigDocController.php b/src/Controller/TwigDocController.php index cbed517..b2e9694 100644 --- a/src/Controller/TwigDocController.php +++ b/src/Controller/TwigDocController.php @@ -11,12 +11,12 @@ use Symfony\Component\HttpKernel\Profiler\Profiler; use Twig\Environment; -class TwigDocController +readonly class TwigDocController { public function __construct( - private readonly Environment $twig, - private readonly ComponentService $componentService, - private readonly ?Profiler $profiler = null + private Environment $twig, + private ComponentService $componentService, + private ?Profiler $profiler = null ) { } diff --git a/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php b/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php index 7476460..a783adf 100644 --- a/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php +++ b/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php @@ -1,5 +1,7 @@ hasExtension('twig_doc')) { return; } + $config = $container->getParameter('twig_doc.config'); $directories = $this->resolveDirectories($container, $config['directories']); $container->getParameterBag()->remove('twig_doc.config'); @@ -51,9 +54,10 @@ public function process(ContainerBuilder $container): void $filename = $file->getFilename(); $componentName = substr($filename, 0, strpos($filename, '.')); - if (array_filter($componentConfig, fn (array $data) => $data['name'] === $componentName)) { - throw new InvalidConfigException(sprintf('component "%s" is configured twice, please configure either directly in the template or the general bundle configuration', $componentName)); + if (array_filter($componentConfig, static fn (array $data) => $data['name'] === $componentName)) { + throw new InvalidConfigException(sprintf('Component "%s" is configured twice, please configure either directly in the template or the general bundle configuration', $componentName)); } + $itemConfig = [ 'name' => $componentName, 'path' => str_replace($projectDir.'/', '', $file->getRealPath()), @@ -69,7 +73,6 @@ public function process(ContainerBuilder $container): void private function parseDoc(SplFileInfo $file, string $docIdentifier): ?array { $content = $file->getContents(); - $pattern = sprintf("/\{#%s\s(.*)%s#}/s", $docIdentifier, $docIdentifier); preg_match($pattern, $content, $matches); @@ -84,10 +87,10 @@ private function parseDoc(SplFileInfo $file, string $docIdentifier): ?array private function enrichComponentsConfig(ContainerBuilder $container, array $directories, array $components): array { foreach ($components as &$component) { - if (!isset($component['path'])) { - $component['path'] = str_replace($container->getParameter('kernel.project_dir').'/', '', $this->getTemplatePath($component['name'], $directories)); + if (!isset($component['path']) && $templatePath = $this->getTemplatePath($component['name'], $directories)) { + $component['path'] = str_replace($container->getParameter('kernel.project_dir').'/', '', $templatePath); } - if (!isset($component['renderPath'])) { + if (!isset($component['renderPath']) && isset($component['path'])) { $component['renderPath'] = str_replace($container->getParameter('twig.default_path').'/', '', $component['path']); } } @@ -98,8 +101,7 @@ private function enrichComponentsConfig(ContainerBuilder $container, array $dire private function resolveDirectories(ContainerBuilder $container, array $directories): array { $directories[] = $container->getParameter('twig.default_path').'/components'; - - $directories = array_map(fn (string $dir) => $container->getParameterBag()->resolveValue($dir), $directories); + $directories = array_map(static fn (string $dir) => $container->getParameterBag()->resolveValue($dir), $directories); foreach ($directories as $idx => $dir) { if (!is_dir($dir)) { @@ -113,9 +115,7 @@ private function resolveDirectories(ContainerBuilder $container, array $director private function getTemplatePath(string $name, array $directories): ?string { $template = sprintf('%s.html.twig', $name); - $finder = new Finder(); - $files = $finder->in($directories)->files()->filter(fn (SplFileInfo $file) => $file->getFilename() === $template); if ($files->count() > 1) { @@ -128,6 +128,6 @@ private function getTemplatePath(string $name, array $directories): ?string $files->getIterator()->rewind(); - return $files->getIterator()->current(); + return $files->getIterator()->current()?->__toString(); } } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 14ead37..a21aeb8 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -1,5 +1,7 @@ subCategories, fn (ComponentCategory $category) => $category->getParent() === $mainCategory); + return array_filter($this->subCategories, static fn (ComponentCategory $category) => $category->getParent() === $mainCategory); } return $this->subCategories; diff --git a/src/Service/ComponentService.php b/src/Service/ComponentService.php index 0f7be91..f45505a 100644 --- a/src/Service/ComponentService.php +++ b/src/Service/ComponentService.php @@ -12,14 +12,14 @@ use Qossmic\TwigDocBundle\Exception\InvalidComponentConfigurationException; use Symfony\Contracts\Cache\CacheInterface; -class ComponentService +readonly class ComponentService { public function __construct( - private readonly ComponentItemFactory $itemFactory, - private readonly array $componentsConfig, - private readonly CacheInterface $cache, - private readonly array $breakpointConfig, - private readonly int $configReadTime = 0 + private ComponentItemFactory $itemFactory, + private array $componentsConfig, + private CacheInterface $cache, + private array $breakpointConfig, + private int $configReadTime = 0 ) { } @@ -94,7 +94,7 @@ public function getInvalidComponents(): array public function getComponent(string $name): ?ComponentItem { - return array_values(array_filter((array) $this->getComponents(), fn (ComponentItem $c) => $c->getName() === $name))[0] ?? null; + return array_values(array_filter((array) $this->getComponents(), static fn (ComponentItem $c) => $c->getName() === $name))[0] ?? null; } public function getBreakpoints(): array diff --git a/templates/blocks/page_blocks.html.twig b/templates/blocks/page_blocks.html.twig index 9951789..e44f0fc 100644 --- a/templates/blocks/page_blocks.html.twig +++ b/templates/blocks/page_blocks.html.twig @@ -37,7 +37,6 @@ {% block body %}
- {% include '@TwigDoc/component/_search.html.twig' %} {% if not components %} diff --git a/templates/component/_invalid_component.html.twig b/templates/component/_invalid_component.html.twig index 68dc9b5..aa6fcee 100644 --- a/templates/component/_invalid_component.html.twig +++ b/templates/component/_invalid_component.html.twig @@ -2,7 +2,7 @@

{{ component.originalConfig.name }}

Path

{{ component.originalConfig.path ?? 'UNKNOWN PATH' }}

-

Error-Details:

+

Error-Details:

{{ component.violationList }}

@@ -24,20 +24,20 @@ {% for tag in component.originalConfig.tags %} {% if loop.first %} + {% endif %} {% endfor %}

Variations:

{% for key, value in variation %} {% if loop.first %} + {% endif %} {% endfor %}
diff --git a/templates/component/_parameter.html.twig b/templates/component/_parameter.html.twig index 074cc2c..a6f1a6f 100644 --- a/templates/component/_parameter.html.twig +++ b/templates/component/_parameter.html.twig @@ -1,7 +1,7 @@ {% if parameter is iterable %} {% else %} diff --git a/templates/component/_viewport.html.twig b/templates/component/_viewport.html.twig index e24cbd5..2c2b5a4 100644 --- a/templates/component/_viewport.html.twig +++ b/templates/component/_viewport.html.twig @@ -1,6 +1,7 @@
-
diff --git a/templates/pages/invalid_components.html.twig b/templates/pages/invalid_components.html.twig index f05bd7e..eb13d41 100644 --- a/templates/pages/invalid_components.html.twig +++ b/templates/pages/invalid_components.html.twig @@ -1,4 +1,4 @@ -{% extends'@TwigDoc/documentation.html.twig' %} +{% extends '@TwigDoc/documentation.html.twig' %} {% block body %}

Invalid Components

diff --git a/tests/Functional/Cache/ComponentsWarmerTest.php b/tests/Functional/Cache/ComponentsWarmerTest.php index 600e90a..edaeb9d 100644 --- a/tests/Functional/Cache/ComponentsWarmerTest.php +++ b/tests/Functional/Cache/ComponentsWarmerTest.php @@ -1,5 +1,7 @@ createMock(ContainerInterface::class)); static::assertTrue($warmer->isOptional()); } diff --git a/tests/Functional/Controller/TwigDocControllerTest.php b/tests/Functional/Controller/TwigDocControllerTest.php index 0158ae6..a6c080e 100644 --- a/tests/Functional/Controller/TwigDocControllerTest.php +++ b/tests/Functional/Controller/TwigDocControllerTest.php @@ -1,5 +1,7 @@ get('validator'), static::getContainer()->get('twig_doc.service.category') ); - self::expectException($expectedExceptionClass); + $this->expectException($expectedExceptionClass); $service->create($componentData); } diff --git a/tests/Functional/Service/ComponentServiceTest.php b/tests/Functional/Service/ComponentServiceTest.php index 56951f1..666a2e8 100644 --- a/tests/Functional/Service/ComponentServiceTest.php +++ b/tests/Functional/Service/ComponentServiceTest.php @@ -1,5 +1,7 @@ get(ComponentService::class); diff --git a/tests/Functional/Twig/TwigDocExtensionTest.php b/tests/Functional/Twig/TwigDocExtensionTest.php index cd17cd0..ac18fe3 100644 --- a/tests/Functional/Twig/TwigDocExtensionTest.php +++ b/tests/Functional/Twig/TwigDocExtensionTest.php @@ -1,5 +1,7 @@ getComponentCategoryMock($componentData['category'], $componentData['sub_category'] ?? null); - $categoryServiceMock = static::createMock(CategoryService::class); + $categoryServiceMock = $this->createMock(CategoryService::class); $categoryServiceMock ->method('getCategory') ->with($componentData['category']) ->willReturn($componentCategoryMock) ; - $validatorMock = static::createMock(ValidatorInterface::class); + $validatorMock = $this->createMock(ValidatorInterface::class); $validatorMock->method('validate') ->willReturn(new ConstraintViolationList()); @@ -42,14 +44,14 @@ public function testValidComponent(array $componentData): void public function testInvalidCategory() { - static::expectException(InvalidComponentConfigurationException::class); + $this->expectException(InvalidComponentConfigurationException::class); - $categoryServiceMock = static::createMock(CategoryService::class); + $categoryServiceMock = $this->createMock(CategoryService::class); $categoryServiceMock ->method('getCategory') ->willReturn(null) ; - $validatorMock = static::createMock(ValidatorInterface::class); + $validatorMock = $this->createMock(ValidatorInterface::class); $componentItemFactory = new ComponentItemFactory($validatorMock, $categoryServiceMock); @@ -65,8 +67,8 @@ public function testGetParamsFromVariables(): void ]; $componentItemFactory = new ComponentItemFactory( - static::createMock(ValidatorInterface::class), - static::createMock(CategoryService::class) + $this->createMock(ValidatorInterface::class), + $this->createMock(CategoryService::class) ); $result = $componentItemFactory->getParamsFromVariables($variables); @@ -147,14 +149,17 @@ public static function getValidComponents(): iterable private function getComponentCategoryMock(string $category, ?string $subCategory = null): ComponentCategory { - $componentCategoryMock = static::createMock(ComponentCategory::class); + $componentCategoryMock = $this->createMock(ComponentCategory::class); $componentCategoryMock->method('getName') ->willReturn($subCategory ?? $category); + $parentMock = null; + if ($subCategory) { - $parentMock = static::createMock(ComponentCategory::class); + $parentMock = $this->createMock(ComponentCategory::class); $parentMock->method('getName')->willReturn($category); } + $componentCategoryMock->method('getParent') ->willReturn($parentMock); diff --git a/tests/Unit/Component/ComponentItemListTest.php b/tests/Unit/Component/ComponentItemListTest.php index dce80e4..88ccc28 100644 --- a/tests/Unit/Component/ComponentItemListTest.php +++ b/tests/Unit/Component/ComponentItemListTest.php @@ -1,5 +1,7 @@ expectException(\InvalidArgumentException::class); $list = new ComponentItemList([]); diff --git a/tests/Unit/Configuration/YamlParserTest.php b/tests/Unit/Configuration/YamlParserTest.php index 5a76102..6345914 100644 --- a/tests/Unit/Configuration/YamlParserTest.php +++ b/tests/Unit/Configuration/YamlParserTest.php @@ -1,5 +1,7 @@ expectException(ParseException::class); $yaml = " key: \nyaml ain't markup language"; $parser = new YamlParser(); - $parser->parse($yaml); } diff --git a/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php b/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php index 8bacbfc..7572243 100644 --- a/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php +++ b/tests/Unit/DependencyInjection/Compiler/TwigDocCollectDocsPassTest.php @@ -1,5 +1,7 @@ process($container); static::assertTrue(true); @@ -40,7 +41,7 @@ public function testProcess(): void static::assertCount(2, $service->getArgument('$componentsConfig')); } - public function testProcessNotEnrichingPathsForMissingTemplate() + public function testProcessNotEnrichingPathsForMissingTemplate(): void { $container = $this->getContainer(componentsConfig: [ [ @@ -49,7 +50,6 @@ public function testProcessNotEnrichingPathsForMissingTemplate() ]); $pass = new TwigDocCollectDocsPass(new YamlParser()); - $pass->process($container); $definition = $container->getDefinition('twig_doc.service.component'); @@ -58,7 +58,7 @@ public function testProcessNotEnrichingPathsForMissingTemplate() static::assertEmpty($definition->getArgument('$componentsConfig')[0]['renderPath']); } - public function testProcessNotEnrichingPathsForAmbiguousTemplate() + public function testProcessNotEnrichingPathsForAmbiguousTemplate(): void { $container = $this->getContainer(componentsConfig: [ [ @@ -72,7 +72,6 @@ public function testProcessNotEnrichingPathsForAmbiguousTemplate() ]); $pass = new TwigDocCollectDocsPass(new YamlParser()); - $pass->process($container); $definition = $container->getDefinition('twig_doc.service.component'); @@ -83,10 +82,11 @@ public function testProcessNotEnrichingPathsForAmbiguousTemplate() static::assertEmpty($definition->getArgument('$componentsConfig')[1]['renderPath']); } - public function testProcessThrowsExceptionForInvalidConfiguration() + public function testProcessThrowsExceptionForInvalidConfiguration(): void { - static::expectException(InvalidConfigException::class); - static::expectExceptionMessage(sprintf('component "%s" is configured twice, please configure either directly in the template or the general bundle configuration', 'Button')); + $this->expectException(InvalidConfigException::class); + $this->expectExceptionMessage(sprintf('Component "%s" is configured twice, please configure either directly in the template or the general bundle configuration', 'Button')); + $container = $this->getContainer([ [ 'name' => 'Button', diff --git a/tests/Unit/DependencyInjection/ConfigurationTest.php b/tests/Unit/DependencyInjection/ConfigurationTest.php index c6d9776..fff3fd9 100644 --- a/tests/Unit/DependencyInjection/ConfigurationTest.php +++ b/tests/Unit/DependencyInjection/ConfigurationTest.php @@ -1,5 +1,7 @@ expectException(InvalidConfigException::class); new CategoryService($config); } - public function testGetCategories() + public function testGetCategories(): void { $service = new CategoryService([['name' => 'Category']]); @@ -56,7 +58,7 @@ public function testGetCategories() static::assertContainsOnlyInstancesOf(ComponentCategory::class, $categories); } - public function testGetCategoryReturnsNullForUnknownCategory() + public function testGetCategoryReturnsNullForUnknownCategory(): void { $service = new CategoryService([['name' => 'Category']]);