diff --git a/Changelog.md b/Changelog.md index 04c02c2..edfe22c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,12 @@ The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. +## UNRELEASED + +### Added + +- Support for Twig2.0 + ## 1.0.0 ## Added diff --git a/composer.json b/composer.json index b90a160..6e9602b 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "php": "^5.5 || ^7.0", "nikic/php-parser": "^3.0", "symfony/finder": "^2.7 || ^3.0", - "twig/twig": "^1.21" + "twig/twig": "^1.27 || ^2.0" }, "require-dev": { "phpunit/phpunit": "^4.5 || ^5.4", diff --git a/src/FileExtractor/TwigFileExtractor.php b/src/FileExtractor/TwigFileExtractor.php index 6e714fa..c859f92 100644 --- a/src/FileExtractor/TwigFileExtractor.php +++ b/src/FileExtractor/TwigFileExtractor.php @@ -33,9 +33,9 @@ final class TwigFileExtractor implements FileExtractor /** * @param \Twig_Environment $twig */ - public function __construct(\Twig_Environment $twig = null) + public function __construct(\Twig_Environment $twig) { - $this->twig = $twig ?: new \Twig_Environment(); + $this->twig = $twig; } public function getSourceLocations(SplFileInfo $file, SourceCollection $collection) @@ -46,7 +46,13 @@ public function getSourceLocations(SplFileInfo $file, SourceCollection $collecti $path = $file->getRelativePath(); - $tokens = $this->twig->parse($this->twig->tokenize($file->getContents(), $path)); + if (class_exists('Twig_Source')) { + // Twig 2.0 + $stream = $this->twig->tokenize(new \Twig_Source($file->getContents(), $file->getRelativePathname(), $path)); + } else { + $stream = $this->twig->tokenize($file->getContents(), $path); + } + $tokens = $this->twig->parse($stream); $traverser = new \Twig_NodeTraverser($this->twig, $this->visitors); $traverser->traverse($tokens); } diff --git a/src/Visitor/Twig/TranslationBlock.php b/src/Visitor/Twig/TranslationBlock.php index 627b5a3..5e0c68e 100644 --- a/src/Visitor/Twig/TranslationBlock.php +++ b/src/Visitor/Twig/TranslationBlock.php @@ -11,8 +11,6 @@ namespace Translation\Extractor\Visitor\Twig; -use Symfony\Bridge\Twig\Node\TransNode; -use Translation\Extractor\Model\SourceLocation; use Translation\Extractor\Visitor\BaseVisitor; use Twig_Environment; use Twig_NodeInterface; @@ -22,20 +20,21 @@ */ final class TranslationBlock extends BaseVisitor implements \Twig_NodeVisitorInterface { - public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) - { - if ($node instanceof TransNode) { - $id = $node->getNode('body')->getAttribute('data'); - $domain = 'messages'; - if ($node->hasNode('domain')) { - $domain = $node->getNode('domain')->getAttribute('value'); - } + /** + * @var WorkerTranslationBlock + */ + private $worker; - $source = new SourceLocation($id, $this->getAbsoluteFilePath(), $node->getLine(), ['domain' => $domain]); - $this->collection->addLocation($source); - } + public function __construct() + { + $this->worker = new WorkerTranslationBlock(); + } - return $node; + public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) + { + return $this->worker->work($node, $this->collection, function () { + return $this->getAbsoluteFilePath(); + }); } public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) diff --git a/src/Visitor/Twig/TranslationFilter.php b/src/Visitor/Twig/TranslationFilter.php index ae61cfc..3845bb7 100644 --- a/src/Visitor/Twig/TranslationFilter.php +++ b/src/Visitor/Twig/TranslationFilter.php @@ -11,7 +11,6 @@ namespace Translation\Extractor\Visitor\Twig; -use Translation\Extractor\Model\SourceLocation; use Translation\Extractor\Visitor\BaseVisitor; use Twig_Environment; use Twig_NodeInterface; @@ -21,40 +20,21 @@ */ final class TranslationFilter extends BaseVisitor implements \Twig_NodeVisitorInterface { - public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) - { - if (!$node instanceof \Twig_Node_Expression_Filter) { - return $node; - } - - $name = $node->getNode('filter')->getAttribute('value'); - if ('trans' !== $name && 'transchoice' !== $name) { - return $node; - } - - $idNode = $node->getNode('node'); - if (!$idNode instanceof \Twig_Node_Expression_Constant) { - // We can only extract constants - return $node; - } - - $id = $idNode->getAttribute('value'); - $index = 'trans' === $name ? 1 : 2; - $domain = 'messages'; - $arguments = $node->getNode('arguments'); - if ($arguments->hasNode($index)) { - $argument = $arguments->getNode($index); - if (!$argument instanceof \Twig_Node_Expression_Constant) { - return $node; - } - - $domain = $argument->getAttribute('value'); - } + /** + * @var WorkerTranslationFilter + */ + private $worker; - $source = new SourceLocation($id, $this->getAbsoluteFilePath(), $node->getLine(), ['domain' => $domain]); - $this->collection->addLocation($source); + public function __construct() + { + $this->worker = new WorkerTranslationFilter(); + } - return $node; + public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) + { + return $this->worker->work($node, $this->collection, function () { + return $this->getAbsoluteFilePath(); + }); } public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) diff --git a/src/Visitor/Twig/Twig2TranslationBlock.php b/src/Visitor/Twig/Twig2TranslationBlock.php new file mode 100644 index 0000000..b974006 --- /dev/null +++ b/src/Visitor/Twig/Twig2TranslationBlock.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Translation\Extractor\Visitor\Twig; + +use Translation\Extractor\Visitor\BaseVisitor; +use Twig_Environment; +use Twig_Node; + +/** + * @author Tobias Nyholm + */ +final class Twig2TranslationBlock extends BaseVisitor implements \Twig_NodeVisitorInterface +{ + /** + * @var WorkerTranslationBlock + */ + private $worker; + + public function __construct() + { + $this->worker = new WorkerTranslationBlock(); + } + + public function enterNode(Twig_Node $node, Twig_Environment $env) + { + return $this->worker->work($node, $this->collection, function () { + return $this->getAbsoluteFilePath(); + }); + } + + public function leaveNode(Twig_Node $node, Twig_Environment $env) + { + return $node; + } + + public function getPriority() + { + return 0; + } +} diff --git a/src/Visitor/Twig/Twig2TranslationFilter.php b/src/Visitor/Twig/Twig2TranslationFilter.php new file mode 100644 index 0000000..d6d7e48 --- /dev/null +++ b/src/Visitor/Twig/Twig2TranslationFilter.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Translation\Extractor\Visitor\Twig; + +use Translation\Extractor\Visitor\BaseVisitor; +use Twig_Environment; +use Twig_Node; + +/** + * @author Tobias Nyholm + */ +final class Twig2TranslationFilter extends BaseVisitor implements \Twig_NodeVisitorInterface +{ + /** + * @var WorkerTranslationFilter + */ + private $worker; + + public function __construct() + { + $this->worker = new WorkerTranslationFilter(); + } + + public function enterNode(Twig_Node $node, Twig_Environment $env) + { + return $this->worker->work($node, $this->collection, function () { + return $this->getAbsoluteFilePath(); + }); + } + + public function leaveNode(Twig_Node $node, Twig_Environment $env) + { + return $node; + } + + public function getPriority() + { + return 0; + } +} diff --git a/src/Visitor/Twig/WorkerTranslationBlock.php b/src/Visitor/Twig/WorkerTranslationBlock.php new file mode 100644 index 0000000..21c5d0a --- /dev/null +++ b/src/Visitor/Twig/WorkerTranslationBlock.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Translation\Extractor\Visitor\Twig; + +use Symfony\Bridge\Twig\Node\TransNode; +use Translation\Extractor\Model\SourceCollection; +use Translation\Extractor\Model\SourceLocation; + +/** + * @author Tobias Nyholm + */ +final class WorkerTranslationBlock +{ + /** + * @param \Twig_Node|\Twig_NodeInterface $node + * @param SourceCollection $collection + * @param callable $getAbsoluteFilePath + * + * @return \Twig_Node|\Twig_NodeInterface + */ + public function work($node, SourceCollection $collection, callable $getAbsoluteFilePath) + { + if ($node instanceof TransNode) { + $id = $node->getNode('body')->getAttribute('data'); + $domain = 'messages'; + if ($node->hasNode('domain')) { + if (null !== $domainNode = $node->getNode('domain')) { + $domain = $domainNode->getAttribute('value'); + } + } + + $source = new SourceLocation($id, $getAbsoluteFilePath(), $node->getTemplateLine(), ['domain' => $domain]); + $collection->addLocation($source); + } + + return $node; + } +} diff --git a/src/Visitor/Twig/WorkerTranslationFilter.php b/src/Visitor/Twig/WorkerTranslationFilter.php new file mode 100644 index 0000000..023563c --- /dev/null +++ b/src/Visitor/Twig/WorkerTranslationFilter.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Translation\Extractor\Visitor\Twig; + +use Translation\Extractor\Model\SourceCollection; +use Translation\Extractor\Model\SourceLocation; + +/** + * @author Tobias Nyholm + */ +final class WorkerTranslationFilter +{ + /** + * @param \Twig_Node|\Twig_NodeInterface $node + * @param SourceCollection $collection + * @param callable $getAbsoluteFilePath + * + * @return \Twig_Node|\Twig_NodeInterface + */ + public function work($node, SourceCollection $collection, callable $getAbsoluteFilePath) + { + if (!$node instanceof \Twig_Node_Expression_Filter) { + return $node; + } + + $name = $node->getNode('filter')->getAttribute('value'); + if ('trans' !== $name && 'transchoice' !== $name) { + return $node; + } + + $idNode = $node->getNode('node'); + if (!$idNode instanceof \Twig_Node_Expression_Constant) { + // We can only extract constants + return $node; + } + + $id = $idNode->getAttribute('value'); + $index = 'trans' === $name ? 1 : 2; + $domain = 'messages'; + $arguments = $node->getNode('arguments'); + if ($arguments->hasNode($index)) { + $argument = $arguments->getNode($index); + if (!$argument instanceof \Twig_Node_Expression_Constant) { + return $node; + } + + $domain = $argument->getAttribute('value'); + } + + $source = new SourceLocation($id, $getAbsoluteFilePath(), $node->getTemplateLine(), ['domain' => $domain]); + $collection->addLocation($source); + + return $node; + } +} diff --git a/tests/Functional/Visitor/Twig/TranslationBlockTest.php b/tests/Functional/Visitor/Twig/TranslationBlockTest.php index edaf618..ffd3a65 100644 --- a/tests/Functional/Visitor/Twig/TranslationBlockTest.php +++ b/tests/Functional/Visitor/Twig/TranslationBlockTest.php @@ -12,15 +12,25 @@ namespace Translation\Extractor\Tests\Functional\Visitor\Twig; use Translation\Extractor\Visitor\Twig\TranslationBlock; +use Translation\Extractor\Visitor\Twig\Twig2TranslationBlock; /** * @author Tobias Nyholm */ final class TranslationBlockTest extends BaseTwigVisitorTest { + private function getVisitor() + { + if (\Twig_Environment::MAJOR_VERSION === 1) { + return new TranslationBlock(); + } else { + return new Twig2TranslationBlock(); + } + } + public function testTrans() { - $collection = $this->getSourceLocations(new TranslationBlock(), 'Twig/TranslationBlock/trans.html.twig'); + $collection = $this->getSourceLocations($this->getVisitor(), 'Twig/TranslationBlock/trans.html.twig'); $this->assertCount(3, $collection); $source = $collection->get(0); @@ -38,7 +48,7 @@ public function testTrans() public function testTranschoice() { - $collection = $this->getSourceLocations(new TranslationBlock(), 'Twig/TranslationBlock/transchoice.html.twig'); + $collection = $this->getSourceLocations($this->getVisitor(), 'Twig/TranslationBlock/transchoice.html.twig'); $this->assertCount(1, $collection); $source = $collection->first(); diff --git a/tests/Functional/Visitor/Twig/TranslationFilterTest.php b/tests/Functional/Visitor/Twig/TranslationFilterTest.php index d2f7409..7828b2b 100644 --- a/tests/Functional/Visitor/Twig/TranslationFilterTest.php +++ b/tests/Functional/Visitor/Twig/TranslationFilterTest.php @@ -12,6 +12,7 @@ namespace Translation\Extractor\Tests\Functional\Visitor\Twig; use Translation\Extractor\Visitor\Twig\TranslationFilter; +use Translation\Extractor\Visitor\Twig\Twig2TranslationFilter; /** * @author Tobias Nyholm @@ -20,7 +21,12 @@ final class TranslationFilterTest extends BaseTwigVisitorTest { public function testExtract() { - $collection = $this->getSourceLocations(new TranslationFilter(), 'Twig/TranslationFilter/trans.html.twig'); + if (\Twig_Environment::MAJOR_VERSION === 1) { + $visitor = new TranslationFilter(); + } else { + $visitor = new Twig2TranslationFilter(); + } + $collection = $this->getSourceLocations($visitor, 'Twig/TranslationFilter/trans.html.twig'); $this->assertCount(1, $collection); $source = $collection->first(); diff --git a/tests/Functional/Visitor/Twig/TwigEnvironmentFactory.php b/tests/Functional/Visitor/Twig/TwigEnvironmentFactory.php index 32a0cab..0a0be30 100644 --- a/tests/Functional/Visitor/Twig/TwigEnvironmentFactory.php +++ b/tests/Functional/Visitor/Twig/TwigEnvironmentFactory.php @@ -24,9 +24,8 @@ final class TwigEnvironmentFactory { public static function create() { - $env = new \Twig_Environment(); + $env = new \Twig_Environment(new \Twig_Loader_Array([])); $env->addExtension(new TranslationExtension($translator = new IdentityTranslator(new MessageSelector()))); - $env->setLoader(new \Twig_Loader_String()); return $env; } diff --git a/tests/Smoke/AllExtractorsTest.php b/tests/Smoke/AllExtractorsTest.php index f7264d8..26afa26 100644 --- a/tests/Smoke/AllExtractorsTest.php +++ b/tests/Smoke/AllExtractorsTest.php @@ -24,6 +24,8 @@ use Translation\Extractor\Visitor\Php\Symfony\FormTypeLabelImplicit; use Translation\Extractor\Visitor\Twig\TranslationBlock; use Translation\Extractor\Visitor\Twig\TranslationFilter; +use Translation\Extractor\Visitor\Twig\Twig2TranslationBlock; +use Translation\Extractor\Visitor\Twig\Twig2TranslationFilter; /** * Smoke test to make sure no extractor throws exceptions. @@ -66,8 +68,14 @@ private function getPHPFileExtractor() private function getTwigFileExtractor() { $file = new TwigFileExtractor(TwigEnvironmentFactory::create()); - $file->addVisitor(new TranslationBlock()); - $file->addVisitor(new TranslationFilter()); + + if (\Twig_Environment::MAJOR_VERSION === 1) { + $file->addVisitor(new TranslationBlock()); + $file->addVisitor(new TranslationFilter()); + } else { + $file->addVisitor(new Twig2TranslationBlock()); + $file->addVisitor(new Twig2TranslationFilter()); + } return $file; }