Skip to content

Commit f278b5a

Browse files
authored
Adding support for @desc annotation (#63)
* Adding support for @desc annotation * style fixes * Removed strict types * Added changelog * Added twig filter * Applied changes from StyleCI * Minor * Test on twig 1
1 parent 04f40ea commit f278b5a

23 files changed

+274
-49
lines changed

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ matrix:
3535
env: DEPENDENCIES="dunglas/symfony-lock:^3"
3636
- php: 7.2
3737
env: DEPENDENCIES="dunglas/symfony-lock:^4"
38+
- php: 5.6
39+
env: DEPENDENCIES="twig/twig:^1.30"
3840

3941
# Latest commit to master
4042
- php: 7.2

Changelog.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ The change log describes what is "Added", "Removed", "Changed" or "Fixed" betwee
44

55
## UNRELEASED
66

7+
## 1.3.0
8+
9+
### Added
10+
11+
- Support for passing choice as variable
12+
- Support for Symfony 4
13+
- Support for `desc` annotation and twig filter
14+
- Support for .phtml
15+
716
## 1.2.0
817

918
### Added

src/Annotation/Desc.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the PHP Translation package.
5+
*
6+
* (c) PHP Translation team <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Translation\Extractor\Annotation;
13+
14+
/**
15+
* @Annotation
16+
*/
17+
final class Desc
18+
{
19+
public $text;
20+
}

src/Twig/TranslationExtension.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the PHP Translation package.
5+
*
6+
* (c) PHP Translation team <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Translation\Extractor\Twig;
13+
14+
final class TranslationExtension extends \Twig_Extension
15+
{
16+
public function getFilters()
17+
{
18+
return [
19+
new \Twig_SimpleFilter('desc', [$this, 'runDescFilter']),
20+
];
21+
}
22+
23+
public function runDescFilter($v)
24+
{
25+
return $v;
26+
}
27+
28+
public function getName()
29+
{
30+
return 'php-translation';
31+
}
32+
}

src/Visitor/BaseVisitor.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
use Doctrine\Common\Annotations\DocParser;
1515
use PhpParser\Node;
1616
use Symfony\Component\Finder\SplFileInfo;
17+
use Translation\Extractor\Annotation\Desc;
1718
use Translation\Extractor\Annotation\Ignore;
1819
use Translation\Extractor\Model\Error;
1920
use Translation\Extractor\Model\SourceCollection;
21+
use Translation\Extractor\Model\SourceLocation;
2022

2123
/**
2224
* Base class for any visitor.
@@ -77,6 +79,30 @@ protected function addError(Node $node, $errorMessage)
7779
$this->collection->addError(new Error($errorMessage, $file, $line));
7880
}
7981

82+
/**
83+
* @param string $text
84+
* @param int $line
85+
* @param Node|null $node
86+
* @param array $context
87+
*/
88+
protected function addLocation($text, $line, Node $node = null, array $context = [])
89+
{
90+
$file = $this->getAbsoluteFilePath();
91+
if (null !== $node && null !== $docComment = $node->getDocComment()) {
92+
$parserContext = 'file '.$file.' near line '.$line;
93+
foreach ($this->getDocParser()->parse($docComment->getText(), $parserContext) as $annotation) {
94+
if ($annotation instanceof Ignore) {
95+
return;
96+
} elseif ($annotation instanceof Desc) {
97+
$context['desc'] = $annotation->text;
98+
}
99+
}
100+
}
101+
102+
$source = new SourceLocation($text, $file, $line, $context);
103+
$this->collection->addLocation($source);
104+
}
105+
80106
/**
81107
* @return DocParser
82108
*/
@@ -87,6 +113,7 @@ private function getDocParser()
87113

88114
$this->docParser->setImports([
89115
'ignore' => Ignore::class,
116+
'desc' => Desc::class,
90117
]);
91118
$this->docParser->setIgnoreNotImportedAnnotations(true);
92119
}

src/Visitor/Php/Symfony/ContainerAwareTrans.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
use PhpParser\Node;
1515
use PhpParser\NodeVisitor;
16-
use Translation\Extractor\Model\SourceLocation;
1716
use Translation\Extractor\Visitor\Php\BasePHPVisitor;
1817

1918
/**
@@ -41,8 +40,7 @@ public function enterNode(Node $node)
4140
$label = $this->getStringArgument($node, 0);
4241
$domain = $this->getStringArgument($node, 2);
4342

44-
$source = new SourceLocation($label, $this->getAbsoluteFilePath(), $node->getAttribute('startLine'), ['domain' => $domain]);
45-
$this->collection->addLocation($source);
43+
$this->addLocation($label, $node->getAttribute('startLine'), $node, ['domain' => $domain]);
4644
}
4745
}
4846

src/Visitor/Php/Symfony/ContainerAwareTransChoice.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
use PhpParser\Node;
1515
use PhpParser\NodeVisitor;
16-
use Translation\Extractor\Model\SourceLocation;
1716
use Translation\Extractor\Visitor\Php\BasePHPVisitor;
1817

1918
/**
@@ -41,8 +40,7 @@ public function enterNode(Node $node)
4140
$label = $this->getStringArgument($node, 0);
4241
$domain = $this->getStringArgument($node, 3);
4342

44-
$source = new SourceLocation($label, $this->getAbsoluteFilePath(), $node->getAttribute('startLine'), ['domain' => $domain]);
45-
$this->collection->addLocation($source);
43+
$this->addLocation($label, $node->getAttribute('startLine'), $node, ['domain' => $domain]);
4644
}
4745
}
4846

src/Visitor/Php/Symfony/FlashMessage.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
use PhpParser\Node;
1515
use PhpParser\NodeVisitor;
16-
use Translation\Extractor\Model\SourceLocation;
1716
use Translation\Extractor\Visitor\Php\BasePHPVisitor;
1817

1918
/**
@@ -48,7 +47,7 @@ public function enterNode(Node $node)
4847
('add' === $name && 'getFlashBag' === $callerName && $caller instanceof Node\Expr\MethodCall)
4948
) {
5049
if (null !== $label = $this->getStringArgument($node, 1)) {
51-
$this->collection->addLocation(new SourceLocation($label, $this->getAbsoluteFilePath(), $node->getAttribute('startLine')));
50+
$this->addLocation($label, $node->getAttribute('startLine'), $node);
5251
}
5352
}
5453
}

src/Visitor/Php/Symfony/FormTypeEmptyValue.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use PhpParser\Node;
1515
use PhpParser\Node\Stmt;
1616
use PhpParser\NodeVisitor;
17-
use Translation\Extractor\Model\SourceLocation;
1817
use Translation\Extractor\Visitor\Php\BasePHPVisitor;
1918

2019
/**
@@ -71,8 +70,7 @@ private function storeValue(Node $node, $item)
7170
return;
7271
}
7372

74-
$sl = new SourceLocation($label, $this->getAbsoluteFilePath(), $node->getAttribute('startLine'));
75-
$this->collection->addLocation($sl);
73+
$this->addLocation($label, $node->getAttribute('startLine'), $node);
7674
}
7775

7876
public function leaveNode(Node $node)

src/Visitor/Php/Symfony/FormTypeInvalidMessage.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use PhpParser\Node;
1515
use PhpParser\Node\Stmt;
1616
use PhpParser\NodeVisitor;
17-
use Translation\Extractor\Model\SourceLocation;
1817
use Translation\Extractor\Visitor\Php\BasePHPVisitor;
1918

2019
/**
@@ -55,8 +54,7 @@ public function enterNode(Node $node)
5554
continue;
5655
}
5756

58-
$sl = new SourceLocation($label, $this->getAbsoluteFilePath(), $node->getAttribute('startLine'), ['domain' => 'validators']);
59-
$this->collection->addLocation($sl);
57+
$this->addLocation($label, $node->getAttribute('startLine'), $node, ['domain' => 'validators']);
6058
}
6159
}
6260

src/Visitor/Php/Symfony/FormTypeLabelExplicit.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use PhpParser\Node;
1515
use PhpParser\Node\Stmt;
1616
use PhpParser\NodeVisitor;
17-
use Translation\Extractor\Model\SourceLocation;
1817
use Translation\Extractor\Visitor\Php\BasePHPVisitor;
1918

2019
/**
@@ -60,8 +59,7 @@ public function enterNode(Node $node)
6059
continue;
6160
}
6261

63-
$sl = new SourceLocation($label, $this->getAbsoluteFilePath(), $node->getAttribute('startLine'));
64-
$this->collection->addLocation($sl);
62+
$this->addLocation($label, $node->getAttribute('startLine'), $item);
6563
}
6664
}
6765

src/Visitor/Php/Symfony/FormTypeLabelImplicit.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use PhpParser\Node;
1515
use PhpParser\Node\Stmt;
1616
use PhpParser\NodeVisitor;
17-
use Translation\Extractor\Model\SourceLocation;
1817
use Translation\Extractor\Visitor\Php\BasePHPVisitor;
1918

2019
/**
@@ -54,8 +53,7 @@ public function enterNode(Node $node)
5453
if (false === $customLabel) {
5554
$label = $node->args[0]->value->value;
5655
if (!empty($label)) {
57-
$sl = new SourceLocation($label, $this->getAbsoluteFilePath(), $node->getAttribute('startLine'));
58-
$this->collection->addLocation($sl);
56+
$this->addLocation($label, $node->getAttribute('startLine'), $node);
5957
}
6058
}
6159
}

src/Visitor/Php/Symfony/FormTypePlaceholder.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use PhpParser\Node;
1515
use PhpParser\Node\Stmt;
1616
use PhpParser\NodeVisitor;
17-
use Translation\Extractor\Model\SourceLocation;
1817
use Translation\Extractor\Visitor\Php\BasePHPVisitor;
1918

2019
/**
@@ -42,9 +41,8 @@ public function enterNode(Node $node)
4241

4342
if ('placeholder' === $item->key->value) {
4443
if ($item->value instanceof Node\Scalar\String_) {
45-
$path = $this->getAbsoluteFilePath();
4644
$line = $item->value->getAttribute('startLine');
47-
$this->collection->addLocation(new SourceLocation($item->value->value, $path, $line));
45+
$this->addLocation($item->value->value, $line, $item);
4846
} else {
4947
$this->addError($item, 'Form placeholder is not a scalar string');
5048
}

src/Visitor/Php/Symfony/ValidationAnnotation.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use PhpParser\Node;
1616
use PhpParser\NodeVisitor;
1717
use Symfony\Component\Validator\Mapping\ClassMetadata;
18-
use Translation\Extractor\Model\SourceLocation;
1918
use Translation\Extractor\Visitor\Php\BasePHPVisitor;
2019
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
2120

@@ -109,7 +108,7 @@ private function extractFromConstraints(array $constraints)
109108
if ('message' === strtolower(substr($propName, -1 * strlen('Message')))) {
110109
// If it is different from the default value
111110
if ($defaultValues[$propName] !== $constraint->{$propName}) {
112-
$this->collection->addLocation(new SourceLocation($constraint->{$propName}, $this->getAbsoluteFilePath(), 0, ['domain' => 'validators']));
111+
$this->addLocation($constraint->{$propName}, 0, null, ['domain' => 'validators']);
113112
}
114113
}
115114
}

src/Visitor/Twig/Worker.php

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Translation\Extractor\Visitor\Twig;
1313

1414
use Symfony\Bridge\Twig\Node\TransNode;
15+
use Translation\Extractor\Model\Error;
1516
use Translation\Extractor\Model\SourceCollection;
1617
use Translation\Extractor\Model\SourceLocation;
1718

@@ -25,6 +26,8 @@ final class Worker
2526
{
2627
const UNDEFINED_DOMAIN = 'messages';
2728

29+
private $stack = [];
30+
2831
/**
2932
* @param \Twig_Node|\Twig_NodeInterface $node
3033
* @param SourceCollection $collection
@@ -34,30 +37,31 @@ final class Worker
3437
*/
3538
public function work($node, SourceCollection $collection, callable $getAbsoluteFilePath)
3639
{
37-
if (
38-
$node instanceof \Twig_Node_Expression_Filter &&
39-
'trans' === $node->getNode('filter')->getAttribute('value') &&
40-
$node->getNode('node') instanceof \Twig_Node_Expression_Constant
41-
) {
42-
// extract constant nodes with a trans filter
43-
$collection->addLocation(new SourceLocation(
44-
$node->getNode('node')->getAttribute('value'),
45-
$getAbsoluteFilePath(),
46-
$node->getTemplateLine(),
47-
['domain' => $this->getReadDomainFromArguments($node->getNode('arguments'), 1)]
48-
));
49-
} elseif (
50-
$node instanceof \Twig_Node_Expression_Filter &&
51-
'transchoice' === $node->getNode('filter')->getAttribute('value') &&
52-
$node->getNode('node') instanceof \Twig_Node_Expression_Constant
53-
) {
54-
// extract constant nodes with a trans filter
55-
$collection->addLocation(new SourceLocation(
56-
$node->getNode('node')->getAttribute('value'),
57-
$getAbsoluteFilePath(),
58-
$node->getTemplateLine(),
59-
['domain' => $this->getReadDomainFromArguments($node->getNode('arguments'), 2)]
60-
));
40+
$this->stack[] = $node;
41+
if ($node instanceof \Twig_Node_Expression_Filter && $node->getNode('node') instanceof \Twig_Node_Expression_Constant) {
42+
$domain = null;
43+
if ('trans' === $node->getNode('filter')->getAttribute('value')) {
44+
$domain = $this->getReadDomainFromArguments($node->getNode('arguments'), 1);
45+
} elseif ('transchoice' === $node->getNode('filter')->getAttribute('value')) {
46+
$domain = $this->getReadDomainFromArguments($node->getNode('arguments'), 2);
47+
}
48+
49+
if ($domain) {
50+
try {
51+
$context = $this->extractContextFromJoinedFilters();
52+
} catch (\LogicException $e) {
53+
$collection->addError(new Error($e->getMessage(), $getAbsoluteFilePath(), $node->getTemplateLine()));
54+
}
55+
$context['domain'] = $domain;
56+
$collection->addLocation(
57+
new SourceLocation(
58+
$node->getNode('node')->getAttribute('value'),
59+
$getAbsoluteFilePath(),
60+
$node->getTemplateLine(),
61+
$context
62+
)
63+
);
64+
}
6165
} elseif ($node instanceof TransNode) {
6266
// extract trans nodes
6367
$domain = self::UNDEFINED_DOMAIN;
@@ -76,6 +80,35 @@ public function work($node, SourceCollection $collection, callable $getAbsoluteF
7680
return $node;
7781
}
7882

83+
/**
84+
* @return array
85+
*/
86+
private function extractContextFromJoinedFilters()
87+
{
88+
$context = [];
89+
for ($i = count($this->stack) - 2; $i >= 0; $i -= 1) {
90+
if (!$this->stack[$i] instanceof \Twig_Node_Expression_Filter) {
91+
break;
92+
}
93+
$name = $this->stack[$i]->getNode('filter')->getAttribute('value');
94+
if ('trans' === $name) {
95+
break;
96+
} elseif ('desc' === $name) {
97+
$arguments = $this->stack[$i]->getNode('arguments');
98+
if (!$arguments->hasNode(0)) {
99+
throw new \LogicException(sprintf('The "%s" filter requires exactly one argument, the description text.', $name));
100+
}
101+
$text = $arguments->getNode(0);
102+
if (!$text instanceof \Twig_Node_Expression_Constant) {
103+
throw new \LogicException(sprintf('The first argument of the "%s" filter must be a constant expression, such as a string.', $name));
104+
}
105+
$context['desc'] = $text->getAttribute('value');
106+
}
107+
}
108+
109+
return $context;
110+
}
111+
79112
/**
80113
* @param \Twig_Node $arguments
81114
* @param int $index

0 commit comments

Comments
 (0)