diff --git a/lib/Core/Input/StringLexer.php b/lib/Core/Input/StringLexer.php index 742296f0..7c38421e 100644 --- a/lib/Core/Input/StringLexer.php +++ b/lib/Core/Input/StringLexer.php @@ -13,6 +13,7 @@ namespace Rollerworks\Component\Search\Input; +use Rollerworks\Component\Search\Exception\BadMethodCallException; use Rollerworks\Component\Search\Exception\StringLexerException; /** @@ -26,7 +27,7 @@ final class StringLexer public const COMPARE = 'compare'; public const RANGE = 'range'; - /** @var array */ + /** @var array */ private array $valueLexers; private string $data; @@ -39,11 +40,12 @@ final class StringLexer private ?int $colSnapshot; private ?int $cursorSnapshot; private ?int $charSnapshot; + private ?string $inValue = null; /** * @internal * - * @param array $fieldLexers + * @param array $fieldLexers */ public function parse(string $data, array $fieldLexers = []): void { @@ -327,9 +329,17 @@ public function fieldIdentification(): string */ public function valuePart(string $fieldName, string $allowedNext = ',;)'): string { + if ($this->inValue) { + $this->throwForbiddenInternalOperation(__METHOD__); + } + // matches value syntax (with custom lexer) or string if (isset($this->valueLexers[$fieldName])) { - return $this->valueLexers[$fieldName]($this, $allowedNext); + $this->inValue = $fieldName; + $value = $this->valueLexers[$fieldName]($this, $allowedNext); + $this->inValue = null; + + return $value; } return $this->stringValue($allowedNext); @@ -342,6 +352,10 @@ public function valuePart(string $fieldName, string $allowedNext = ',;)'): strin */ public function rangeValue(string $name): array { + if ($this->inValue) { + $this->throwForbiddenInternalOperation(__METHOD__); + } + $lowerInclusive = ($this->matchOptional('/[[\]]/A') ?? '[') === '['; $this->skipWhitespace(); @@ -367,6 +381,10 @@ public function rangeValue(string $name): array */ public function comparisonValue(string $name): array { + if ($this->inValue) { + $this->throwForbiddenInternalOperation(__METHOD__); + } + $operator = $this->expects('/<>|(?:[<>]=?)/A', 'CompareOperator'); $this->skipWhitespace(); @@ -383,6 +401,10 @@ public function comparisonValue(string $name): array */ public function patternMatchValue(): array { + if ($this->inValue) { + $this->throwForbiddenInternalOperation(__METHOD__); + } + $this->expects('~'); if ($this->cursor === $this->end) { @@ -436,6 +458,10 @@ public function patternMatchValue(): array */ public function detectValueType(string $name): string { + if ($this->inValue) { + $this->throwForbiddenInternalOperation(__METHOD__); + } + if ($this->cursor === $this->end) { return ''; } @@ -495,4 +521,9 @@ private function regexOrSingleChar(string $data): ?string return null; } + + private function throwForbiddenInternalOperation(string $method): never + { + throw new BadMethodCallException(\sprintf('Cannot call "%s" inside a custom value-lexer for field "%s".', mb_substr($method, mb_strrpos($method, '::') + 2), $this->inValue)); + } } diff --git a/lib/Core/Tests/Input/StringLexerTest.php b/lib/Core/Tests/Input/StringLexerTest.php index b567c600..28c73cd8 100644 --- a/lib/Core/Tests/Input/StringLexerTest.php +++ b/lib/Core/Tests/Input/StringLexerTest.php @@ -14,6 +14,7 @@ namespace Rollerworks\Component\Search\Tests\Input; use PHPUnit\Framework\TestCase; +use Rollerworks\Component\Search\Exception\BadMethodCallException; use Rollerworks\Component\Search\Exception\StringLexerException; use Rollerworks\Component\Search\Input\StringLexer; @@ -97,4 +98,34 @@ public function it_reports_the_correct_col_when_start_at_newline(): void $this->lexer->stringValue(); } + + /** + * @dataProvider provideInternalLexerMethods + * + * @test + */ + public function it_forbids_internal_lexer_methods_in_custom_value_lexer(string $method): void + { + $customLexer = static function (StringLexer $lexer, string $expectedNext) use ($method): string { + $lexer->{$method}('custom'); + + return 'error'; + }; + + $this->lexer->parse('id: 12;', ['id' => $customLexer]); + $this->lexer->fieldIdentification(); + + $this->expectExceptionObject(new BadMethodCallException(\sprintf('Cannot call "%s" inside a custom value-lexer for field "%s".', $method, 'id'))); + + $this->lexer->valuePart('id'); + } + + public static function provideInternalLexerMethods(): iterable + { + yield ['valuePart']; + yield ['rangeValue']; + yield ['comparisonValue']; + yield ['patternMatchValue']; + yield ['detectValueType']; + } }