diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index bf649e6..4def910 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -14,8 +14,3 @@ parameters: message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$args\\.$#" count: 1 path: src/Visitor/Php/Symfony/AbstractFormType.php - - - - message: "#^Access to an undefined property PhpParser\\\\Node\\\\Expr\\:\\:\\$value\\.$#" - count: 1 - path: src/Visitor/Php/Symfony/FormTypePlaceholder.php diff --git a/src/Visitor/Php/Symfony/FormTypePlaceholder.php b/src/Visitor/Php/Symfony/FormTypePlaceholder.php index 58998d6..2311baf 100644 --- a/src/Visitor/Php/Symfony/FormTypePlaceholder.php +++ b/src/Visitor/Php/Symfony/FormTypePlaceholder.php @@ -55,6 +55,9 @@ public function enterNode(Node $node): ?Node $placeholderNode = $item; } elseif ('attr' === $item->key->value && $item->value instanceof Node\Expr\Array_) { foreach ($item->value->items as $attrValue) { + if (!$attrValue->key instanceof Node\Scalar\String_) { + continue; + } if ('placeholder' === $attrValue->key->value) { $placeholderNode = $attrValue; diff --git a/src/Visitor/Php/Symfony/FormTypeTitle.php b/src/Visitor/Php/Symfony/FormTypeTitle.php new file mode 100644 index 0000000..571f0af --- /dev/null +++ b/src/Visitor/Php/Symfony/FormTypeTitle.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Translation\Extractor\Visitor\Php\Symfony; + +use PhpParser\Node; +use PhpParser\NodeVisitor; + +/** + * @author Tobias Nyholm + */ +final class FormTypeTitle extends AbstractFormType implements NodeVisitor +{ + use FormTrait; + + /** + * {@inheritdoc} + */ + public function enterNode(Node $node): ?Node + { + if (!$this->isFormType($node)) { + return null; + } + + parent::enterNode($node); + + if (!$node instanceof Node\Expr\Array_) { + return null; + } + + $titleNode = null; + $domain = null; + foreach ($node->items as $item) { + if (!$item->key instanceof Node\Scalar\String_) { + continue; + } + if ('translation_domain' === $item->key->value) { + // Try to find translation domain + if ($item->value instanceof Node\Scalar\String_) { + $domain = $item->value->value; + } elseif ($item->value instanceof Node\Expr\ConstFetch && 'false' === $item->value->name->toString()) { + $domain = false; + } + } elseif ('attr' === $item->key->value && $item->value instanceof Node\Expr\Array_) { + foreach ($item->value->items as $attrValue) { + if (!$attrValue->key instanceof Node\Scalar\String_) { + continue; + } + if ('title' === $attrValue->key->value) { + $titleNode = $attrValue; + + break; + } + } + } + } + + if (null === $titleNode) { + return null; + } + + if ($titleNode->value instanceof Node\Scalar\String_) { + $line = $titleNode->value->getAttribute('startLine'); + if (null !== $location = $this->getLocation($titleNode->value->value, $line, $titleNode, ['domain' => $domain])) { + $this->lateCollect($location); + } + } else { + $this->addError($titleNode, 'Form field title is not a scalar string'); + } + + return null; + } +} diff --git a/tests/Functional/Visitor/Php/Symfony/FormTypeLabelTest.php b/tests/Functional/Visitor/Php/Symfony/FormTypeLabelTest.php index 1f1997f..5f19419 100644 --- a/tests/Functional/Visitor/Php/Symfony/FormTypeLabelTest.php +++ b/tests/Functional/Visitor/Php/Symfony/FormTypeLabelTest.php @@ -19,6 +19,7 @@ use Translation\Extractor\Visitor\Php\Symfony\FormTypeLabelExplicit; use Translation\Extractor\Visitor\Php\Symfony\FormTypeLabelImplicit; use Translation\Extractor\Visitor\Php\Symfony\FormTypePlaceholder; +use Translation\Extractor\Visitor\Php\Symfony\FormTypeTitle; /** * @author Rein Baarsma @@ -36,6 +37,7 @@ public function __construct() new FormTypeLabelExplicit(), new FormTypeLabelImplicit(), new FormTypePlaceholder(), + new FormTypeTitle(), ]; parent::__construct(); diff --git a/tests/Functional/Visitor/Php/Symfony/FormTypeTitleTest.php b/tests/Functional/Visitor/Php/Symfony/FormTypeTitleTest.php new file mode 100644 index 0000000..c69d416 --- /dev/null +++ b/tests/Functional/Visitor/Php/Symfony/FormTypeTitleTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Translation\Extractor\Tests\Functional\Visitor\Php\Symfony; + +use Translation\Extractor\Tests\Functional\Visitor\Php\BasePHPVisitorTest; +use Translation\Extractor\Tests\Resources; +use Translation\Extractor\Visitor\Php\Symfony\ContainerAwareTrans; +use Translation\Extractor\Visitor\Php\Symfony\FormTypeTitle; + +/** + * @author Tobias Nyholm + */ +final class FormTypeTitleTest extends BasePHPVisitorTest +{ + public function testExtract() + { + $collection = $this->getSourceLocations(new FormTypeTitle(), + Resources\Php\Symfony\TitleFormType::class); + + $this->assertCount(1, $collection); + $this->assertEquals('form.title.text', $collection->get(0)->getMessage()); + } + + public function testExtractError() + { + $collection = $this->getSourceLocations(new FormTypeTitle(), + Resources\Php\Symfony\TitleFormErrorType::class); + + $errors = $collection->getErrors(); + $this->assertCount(1, $errors); + } + + public function testChildVisitationNotBlocked() + { + $collection = $this->getSourceLocations( + [ + new FormTypeTitle(), + new ContainerAwareTrans(), + ], + Resources\Php\Symfony\ContainerAwareTrans::class + ); + + $this->assertCount(6, $collection); + + $this->assertEquals('trans0', $collection->get(0)->getMessage()); + $this->assertEquals('trans1', $collection->get(1)->getMessage()); + $this->assertEquals('trans_line', $collection->get(2)->getMessage()); + $this->assertEquals('variable', $collection->get(3)->getMessage()); + $this->assertEquals('my.pdf', $collection->get(4)->getMessage()); + $this->assertEquals('bar', $collection->get(5)->getMessage()); + } +} diff --git a/tests/Resources/Php/Symfony/TitleFormErrorType.php b/tests/Resources/Php/Symfony/TitleFormErrorType.php new file mode 100644 index 0000000..f2327d7 --- /dev/null +++ b/tests/Resources/Php/Symfony/TitleFormErrorType.php @@ -0,0 +1,20 @@ +add('field_with_attr_title', 'text', array( + 'label' => 'field.with.title', + 'attr' => array('title' => $string) + )) + ; + } +} diff --git a/tests/Resources/Php/Symfony/TitleFormType.php b/tests/Resources/Php/Symfony/TitleFormType.php new file mode 100644 index 0000000..c760c8a --- /dev/null +++ b/tests/Resources/Php/Symfony/TitleFormType.php @@ -0,0 +1,19 @@ +add('field_with_attr_title', 'text', array( + 'label' => 'field.with.title', + 'attr' => array('title' => 'form.title.text') + )) + ; + } +} diff --git a/tests/Smoke/AllExtractorsTest.php b/tests/Smoke/AllExtractorsTest.php index 986ddcc..fc565a7 100644 --- a/tests/Smoke/AllExtractorsTest.php +++ b/tests/Smoke/AllExtractorsTest.php @@ -30,6 +30,7 @@ use Translation\Extractor\Visitor\Php\Symfony\FormTypeLabelExplicit; use Translation\Extractor\Visitor\Php\Symfony\FormTypeLabelImplicit; use Translation\Extractor\Visitor\Php\Symfony\FormTypePlaceholder; +use Translation\Extractor\Visitor\Php\Symfony\FormTypeTitle; use Translation\Extractor\Visitor\Php\TranslateAnnotationVisitor; use Translation\Extractor\Visitor\Twig\TwigVisitor; @@ -89,7 +90,7 @@ public function testNoException() * It is okey to increase the error count if you adding more fixtures/code. * We just need to be aware that it changes. */ - $this->assertCount(12, $sc->getErrors(), 'There was an unexpected number of errors. Please investigate.'); + $this->assertCount(13, $sc->getErrors(), 'There was an unexpected number of errors. Please investigate.'); } /** @@ -149,6 +150,7 @@ private function getPHPFileExtractor(): PHPFileExtractor $file->addVisitor(new FormTypeLabelExplicit()); $file->addVisitor(new FormTypeLabelImplicit()); $file->addVisitor(new FormTypePlaceholder()); + $file->addVisitor(new FormTypeTitle()); $file->addVisitor(new SourceLocationContainerVisitor()); $file->addVisitor(new TranslateAnnotationVisitor());