Skip to content

Commit 32905a3

Browse files
authored
fix: parse only string literals (#16)
1 parent 9e03779 commit 32905a3

5 files changed

Lines changed: 92 additions & 6 deletions

File tree

src/Extractor/Parser/Descriptor/DescriptorCollectorVisitor.php

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
use function assert;
3838
use function in_array;
3939
use function preg_replace;
40+
use function sprintf;
4041
use function trim;
4142

4243
/**
@@ -200,9 +201,8 @@ private function parseDescriptorProperties(Node\Expr\Array_ $descriptor): array
200201

201202
assert($item !== null);
202203
assert($item->key instanceof Node\Scalar\String_);
203-
assert($item->value instanceof Node\Scalar\String_);
204204

205-
$properties[$item->key->value] = $item->value->value;
205+
$properties[$item->key->value] = $this->getValue($item->value);
206206
}
207207

208208
return $properties;
@@ -212,7 +212,7 @@ private function isValidDescriptorItem(?Node\Expr\ArrayItem $item): bool
212212
{
213213
return $item !== null
214214
&& $item->key instanceof Node\Scalar\String_
215-
&& $item->value instanceof Node\Scalar\String_;
215+
&& $this->isValidValue($item->value);
216216
}
217217

218218
private function clean(?string $value): ?string
@@ -238,4 +238,35 @@ private function ensureId(DescriptorInterface $descriptor): DescriptorInterface
238238

239239
return $descriptor;
240240
}
241+
242+
private function isValidValue(Node\Expr $value): bool
243+
{
244+
if ($value instanceof Node\Scalar\String_) {
245+
return true;
246+
}
247+
248+
if ($value instanceof Node\Expr\BinaryOp\Concat) {
249+
$isValid = $this->isValidValue($value->left);
250+
251+
return $isValid && $this->isValidValue($value->right);
252+
}
253+
254+
throw new UnableToParseDescriptorException(sprintf(
255+
'The descriptor must not contain values other than string literals; encountered %s',
256+
$value->getType(),
257+
));
258+
}
259+
260+
private function getValue(Node\Expr $value): string
261+
{
262+
if ($value instanceof Node\Scalar\String_) {
263+
return $value->value;
264+
}
265+
266+
assert($value instanceof Node\Expr\BinaryOp\Concat);
267+
268+
$contents = $this->getValue($value->left);
269+
270+
return $contents . $this->getValue($value->right);
271+
}
241272
}

tests/Extractor/MessageExtractorTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,12 @@ public function testProcessWithCustomParser(): void
424424
. '/Parser/Descriptor/fixtures/php-parser-04.php on line 40',
425425
'Descriptor argument must be present in ' . __DIR__
426426
. '/Parser/Descriptor/fixtures/php-parser-09.phtml on line 18',
427+
'The descriptor must not contain values other than string literals; '
428+
. 'encountered Expr_Variable in ' . __DIR__
429+
. '/Parser/Descriptor/fixtures/php-parser-10.php on line 6',
430+
'The descriptor must not contain values other than string literals; '
431+
. 'encountered Scalar_Encapsed in ' . __DIR__
432+
. '/Parser/Descriptor/fixtures/php-parser-10.php on line 12',
427433
'Missing "defaultMessage" in "{{#formatMessage |idWithoutMessage}}{{/formatMessage}}" in '
428434
. __DIR__ . '/Parser/Descriptor/fixtures/custom-parser-01.template on line 0',
429435
'Missing "id" in "{{#formatMessage}}message without ID{{/formatMessage}}" in '
@@ -499,6 +505,12 @@ public function testProcessWithCustomParserAsClosure(): void
499505
. '/Parser/Descriptor/fixtures/php-parser-04.php on line 40',
500506
'Descriptor argument must be present in ' . __DIR__
501507
. '/Parser/Descriptor/fixtures/php-parser-09.phtml on line 18',
508+
'The descriptor must not contain values other than string literals; '
509+
. 'encountered Expr_Variable in ' . __DIR__
510+
. '/Parser/Descriptor/fixtures/php-parser-10.php on line 6',
511+
'The descriptor must not contain values other than string literals; '
512+
. 'encountered Scalar_Encapsed in ' . __DIR__
513+
. '/Parser/Descriptor/fixtures/php-parser-10.php on line 12',
502514
'Missing "defaultMessage" in "{{#formatMessage |idWithoutMessage}}{{/formatMessage}}" in '
503515
. __DIR__ . '/Parser/Descriptor/fixtures/custom-parser-01.template on line 0',
504516
'Missing "id" in "{{#formatMessage}}message without ID{{/formatMessage}}" in '

tests/Extractor/Parser/Descriptor/PhpParserTest.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function testParse01(): void
3131
[
3232
'defaultMessage' => 'This is a default message',
3333
'description' => 'A simple description of a fixture for testing purposes.',
34-
'end' => 202,
34+
'end' => 326,
3535
'file' => __DIR__ . '/fixtures/php-parser-01.php',
3636
'id' => 'aTestId',
3737
'line' => 3,
@@ -323,6 +323,29 @@ public function testParse09(): void
323323
);
324324
}
325325

326+
public function testParse10(): void
327+
{
328+
$errors = new ParserErrorCollection();
329+
$options = new MessageExtractorOptions();
330+
$parser = new PhpParser(new FileSystemHelper());
331+
$descriptors = $parser(__DIR__ . '/fixtures/php-parser-10.php', $options, $errors);
332+
$receivedErrors = $this->compileErrors($errors);
333+
334+
$this->assertContainsOnlyInstancesOf(DescriptorInterface::class, $descriptors);
335+
$this->assertCount(0, $descriptors);
336+
$this->assertSame(
337+
[
338+
'The descriptor must not contain values other than string literals; '
339+
. 'encountered Expr_Variable on line 6 in '
340+
. __DIR__ . '/fixtures/php-parser-10.php',
341+
'The descriptor must not contain values other than string literals; '
342+
. 'encountered Scalar_Encapsed on line 12 in '
343+
. __DIR__ . '/fixtures/php-parser-10.php',
344+
],
345+
$receivedErrors,
346+
);
347+
}
348+
326349
/**
327350
* @return string[]
328351
*/

tests/Extractor/Parser/Descriptor/fixtures/php-parser-01.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
$internationalization->formatMessage([
44
'id' => 'aTestId',
5-
'defaultMessage' => 'This is a default message',
6-
'description' => 'A simple description of a fixture for testing purposes.',
5+
// We're using concatenated string literals on purpose, to test this functionality.
6+
'defaultMessage' => 'This is a ' . 'default ' . 'message',
7+
'description' => 'A simple description '
8+
. 'of a fixture '
9+
. 'for testing purposes.',
710
]);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
$bar = 'default ';
4+
$description = 'description';
5+
6+
$internationalization->formatMessage([
7+
'id' => 'message.with.variable.concat',
8+
// Injecting a variable here to break things.
9+
'defaultMessage' => 'This is a ' . $bar . 'message',
10+
]);
11+
12+
$internationalization->formatMessage([
13+
'id' => 'message.with.variable.interpolated',
14+
// Injecting a variable here to break things.
15+
'defaultMessage' => 'This is a default message',
16+
'description' => "This is a $description with an interpolated variable.",
17+
]);

0 commit comments

Comments
 (0)