Skip to content

Commit b9c4c4a

Browse files
author
Nicolas Oelgart
committed
Improve type checks
1 parent e32e271 commit b9c4c4a

File tree

11 files changed

+109
-13
lines changed

11 files changed

+109
-13
lines changed

src/Grammar/CallableUserFunctionInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
*/
88
namespace nicoSWD\Rule\Grammar;
99

10+
use nicoSWD\Rule\Parser\Exception\ParserException;
1011
use nicoSWD\Rule\TokenStream\Token\BaseToken;
1112

1213
interface CallableUserFunctionInterface
1314
{
15+
/** @throws ParserException */
1416
public function call(?BaseToken ...$param): BaseToken;
1517
}

src/Grammar/JavaScript/Methods/EndsWith.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,29 @@
88
namespace nicoSWD\Rule\Grammar\JavaScript\Methods;
99

1010
use nicoSWD\Rule\Grammar\CallableFunction;
11+
use nicoSWD\Rule\Parser\Exception\ParserException;
1112
use nicoSWD\Rule\TokenStream\Token\BaseToken;
1213
use nicoSWD\Rule\TokenStream\Token\TokenBool;
14+
use nicoSWD\Rule\TokenStream\Token\TokenString;
1315

1416
final class EndsWith extends CallableFunction
1517
{
1618
public function call(?BaseToken ...$parameters): BaseToken
1719
{
20+
if (!$this->token instanceof TokenString) {
21+
throw new ParserException('Call to undefined method "endsWith" on non-string');
22+
}
23+
1824
$needle = $this->parseParameter($parameters, 0);
19-
$value = strpos($this->token->getValue(), $needle->getValue());
25+
$haystack = $this->token->getValue();
26+
27+
if (!$needle) {
28+
$result = false;
29+
} else {
30+
$needle = $needle->getValue();
31+
$result = substr($haystack, -strlen($needle)) === $needle;
32+
}
2033

21-
return new TokenBool($value === strlen($needle->getValue()) - 1);
34+
return new TokenBool($result);
2235
}
2336
}

src/Grammar/JavaScript/Methods/StartsWith.php

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,35 @@
88
namespace nicoSWD\Rule\Grammar\JavaScript\Methods;
99

1010
use nicoSWD\Rule\Grammar\CallableFunction;
11+
use nicoSWD\Rule\Parser\Exception\ParserException;
1112
use nicoSWD\Rule\TokenStream\Token\BaseToken;
1213
use nicoSWD\Rule\TokenStream\Token\TokenBool;
14+
use nicoSWD\Rule\TokenStream\Token\TokenInteger;
15+
use nicoSWD\Rule\TokenStream\Token\TokenString;
1316

1417
final class StartsWith extends CallableFunction
1518
{
1619
public function call(?BaseToken ...$parameters): BaseToken
1720
{
21+
if (!$this->token instanceof TokenString) {
22+
throw new ParserException('Call to undefined method "startsWith" on non-string');
23+
}
24+
1825
$needle = $this->parseParameter($parameters, 0);
19-
$offset = $this->parseParameter($parameters, 1);
26+
$offset = $this->getOffset($this->parseParameter($parameters, 1));
27+
$position = strpos($this->token->getValue(), $needle->getValue(), $offset);
28+
29+
return new TokenBool($position === $offset);
30+
}
2031

21-
if ($offset) {
32+
private function getOffset(?BaseToken $offset): int
33+
{
34+
if ($offset instanceof TokenInteger) {
2235
$offset = $offset->getValue();
2336
} else {
2437
$offset = 0;
2538
}
2639

27-
$value = strpos($this->token->getValue(), $needle->getValue(), $offset);
28-
29-
return new TokenBool($value === $offset);
40+
return $offset;
3041
}
3142
}

src/Parser/Exception/ParserException.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,9 @@ public static function unexpectedComma(BaseToken $token): self
5050
{
5151
return new self(sprintf('Unexpected "," at position %d', $token->getOffset()));
5252
}
53+
54+
public static function unexpectedEndOfString(): self
55+
{
56+
return new self('Unexpected end of string');
57+
}
5358
}

src/TokenStream/AST.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Closure;
1111
use InvalidArgumentException;
1212
use nicoSWD\Rule\Grammar\CallableUserFunctionInterface;
13+
use nicoSWD\Rule\Parser\Exception\ParserException;
1314
use nicoSWD\Rule\TokenStream\Exception\UndefinedVariableException;
1415
use nicoSWD\Rule\TokenStream\Token\BaseToken;
1516
use nicoSWD\Rule\TokenStream\Token\TokenFactory;
@@ -50,6 +51,10 @@ public function getStream(string $rule): TokenStream
5051
return $this->tokenStreamFactory->create($this->tokenizer->tokenize($rule), $this);
5152
}
5253

54+
/**
55+
* @throws Exception\UndefinedMethodException
56+
* @throws Exception\ForbiddenMethodException
57+
*/
5358
public function getMethod(string $methodName, BaseToken $token): CallableUserFunctionInterface
5459
{
5560
if ($token instanceof TokenObject) {
@@ -72,6 +77,10 @@ public function setVariables(array $variables): void
7277
$this->variables = $variables;
7378
}
7479

80+
/**
81+
* @throws UndefinedVariableException
82+
* @throws ParserException
83+
*/
7584
public function getVariable(string $variableName): BaseToken
7685
{
7786
if (!$this->variableExists($variableName)) {
@@ -86,6 +95,7 @@ public function variableExists(string $variableName): bool
8695
return array_key_exists($variableName, $this->variables);
8796
}
8897

98+
/** @throws Exception\UndefinedFunctionException */
8999
public function getFunction(string $functionName): Closure
90100
{
91101
if (empty($this->functions)) {

src/TokenStream/CallableUserMethod.php

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ final class CallableUserMethod implements CallableUserFunctionInterface
2222
/** @var string[] */
2323
private $methodPrefixes = ['', 'get', 'is', 'get_', 'is_'];
2424

25+
/**
26+
* @throws Exception\UndefinedMethodException
27+
* @throws Exception\ForbiddenMethodException
28+
*/
2529
public function __construct(BaseToken $token, TokenFactory $tokenFactory, string $methodName)
2630
{
2731
$this->tokenFactory = $tokenFactory;
@@ -37,6 +41,10 @@ public function call(?BaseToken ...$param): BaseToken
3741
);
3842
}
3943

44+
/**
45+
* @throws Exception\UndefinedMethodException
46+
* @throws Exception\ForbiddenMethodException
47+
*/
4048
private function getCallable(BaseToken $token, string $methodName): callable
4149
{
4250
$object = $token->getValue();
@@ -54,6 +62,10 @@ private function getCallable(BaseToken $token, string $methodName): callable
5462
};
5563
}
5664

65+
/**
66+
* @throws Exception\UndefinedMethodException
67+
* @throws Exception\ForbiddenMethodException
68+
*/
5769
private function findCallableMethod($object, string $methodName): callable
5870
{
5971
$this->assertNonMagicMethod($methodName);
@@ -74,13 +86,16 @@ private function findCallableMethod($object, string $methodName): callable
7486

7587
private function getTokenValues(array $params): array
7688
{
77-
$callback = function (BaseToken $token) {
78-
return $token->getValue();
79-
};
89+
$values = [];
90+
91+
foreach ($params as $token) {
92+
$values[] = $token->getValue();
93+
}
8094

81-
return array_map($callback, $params);
95+
return $values;
8296
}
8397

98+
/** @throws Exception\ForbiddenMethodException */
8499
private function assertNonMagicMethod(string $methodName): void
85100
{
86101
if (substr($methodName, 0, 2) === self::MAGIC_METHOD_PREFIX) {

src/TokenStream/Node/BaseNode.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public function __construct(TokenStream $tokenStream)
2929
$this->tokenStream = $tokenStream;
3030
}
3131

32+
/** @throws ParserException */
3233
abstract public function getNode(): BaseToken;
3334

3435
protected function hasMethodCall(): bool
@@ -61,6 +62,7 @@ protected function hasMethodCall(): bool
6162
return $hasMethod;
6263
}
6364

65+
/** @throws ParserException */
6466
protected function getMethod(BaseToken $token): CallableUserFunctionInterface
6567
{
6668
$this->tokenStream->getStack()->seek($this->methodOffset);
@@ -73,21 +75,24 @@ private function getMethodName(): string
7375
return (string) preg_replace('~\W~', '', $this->methodName);
7476
}
7577

78+
/** @throws ParserException */
7679
protected function getFunction(): Closure
7780
{
7881
return $this->tokenStream->getFunction($this->getFunctionName());
7982
}
8083

8184
private function getFunctionName(): string
8285
{
83-
return preg_replace('~\W~', '', $this->getCurrentNode()->getValue());
86+
return (string) preg_replace('~\W~', '', $this->getCurrentNode()->getValue());
8487
}
8588

89+
/** @throws ParserException */
8690
protected function getArrayItems(): TokenCollection
8791
{
8892
return $this->getCommaSeparatedValues(TokenType::SQUARE_BRACKET);
8993
}
9094

95+
/** @throws ParserException */
9196
protected function getArguments(): TokenCollection
9297
{
9398
return $this->getCommaSeparatedValues(TokenType::PARENTHESIS);
@@ -98,6 +103,7 @@ protected function getCurrentNode(): BaseToken
98103
return $this->tokenStream->getStack()->current();
99104
}
100105

106+
/** @throws ParserException */
101107
private function getCommaSeparatedValues(int $stopAt): TokenCollection
102108
{
103109
$items = new TokenCollection();
@@ -132,17 +138,19 @@ private function getCommaSeparatedValues(int $stopAt): TokenCollection
132138
return $items;
133139
}
134140

141+
/** @throws ParserException */
135142
private function getNextToken(): BaseToken
136143
{
137144
$this->tokenStream->next();
138145

139146
if (!$this->tokenStream->valid()) {
140-
throw new ParserException('Unexpected end of string');
147+
throw ParserException::unexpectedEndOfString();
141148
}
142149

143150
return $this->tokenStream->current();
144151
}
145152

153+
/** @throws ParserException */
146154
private function assertNoTrailingComma(bool $commaExpected, TokenCollection $items, BaseToken $token): void
147155
{
148156
if (!$commaExpected && $items->count() > 0) {

src/TokenStream/Token/BaseToken.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
namespace nicoSWD\Rule\TokenStream\Token;
99

10+
use nicoSWD\Rule\Parser\Exception\ParserException;
1011
use nicoSWD\Rule\TokenStream\TokenStream;
1112

1213
abstract class BaseToken
@@ -39,6 +40,7 @@ public function getOffset(): int
3940
return $this->offset;
4041
}
4142

43+
/** @throws ParserException */
4244
public function createNode(TokenStream $tokenStream): self
4345
{
4446
return $this;

src/TokenStream/TokenStream.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public function valid(): bool
3636
return $this->stack->valid();
3737
}
3838

39+
/** @throws ParserException */
3940
public function current(): BaseToken
4041
{
4142
return $this->getCurrentToken()->createNode($this);
@@ -62,6 +63,7 @@ private function getCurrentToken(): BaseToken
6263
return $this->stack->current();
6364
}
6465

66+
/** @throws ParserException */
6567
public function getVariable(string $variableName): BaseToken
6668
{
6769
try {
@@ -71,6 +73,7 @@ public function getVariable(string $variableName): BaseToken
7173
}
7274
}
7375

76+
/** @throws ParserException */
7477
public function getFunction(string $functionName): Closure
7578
{
7679
try {
@@ -80,6 +83,7 @@ public function getFunction(string $functionName): Closure
8083
}
8184
}
8285

86+
/** @throws ParserException */
8387
public function getMethod(string $methodName, BaseToken $token): CallableUserFunctionInterface
8488
{
8589
try {

tests/integration/methods/EndsWithTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
namespace nicoSWD\Rule\tests\integration\methods;
99

10+
use nicoSWD\Rule\Parser\Exception\ParserException;
1011
use nicoSWD\Rule\tests\integration\AbstractTestBase;
1112

1213
final class EndsWithTest extends AbstractTestBase
@@ -23,4 +24,19 @@ public function givenAStringWhenNotEndsWithNeedleItShouldReturnFalse(): void
2324
{
2425
$this->assertTrue($this->evaluate('"hello".endsWith("ell") === false'));
2526
}
27+
28+
/** @test */
29+
public function givenAStringWhenTestedWithEndsWithWithoutArgsItShouldReturnFalse(): void
30+
{
31+
$this->assertTrue($this->evaluate('"hello".endsWith() === false'));
32+
}
33+
34+
/** @test */
35+
public function givenAStringWhenTestedOnNonStringValuesItShouldThrowAnException(): void
36+
{
37+
$this->expectException(ParserException::class);
38+
$this->expectExceptionMessage('Call to undefined method "endsWith" on non-string');
39+
40+
$this->evaluate('["hello"].endsWith() === false');
41+
}
2642
}

0 commit comments

Comments
 (0)