Skip to content

Improve assert for contains / startsWith / endsWith #186

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 143 additions & 10 deletions src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -754,18 +754,17 @@ private function getExpressionResolvers(): array
)
);
},
'contains' => static function (Scope $scope, Arg $value, Arg $subString): array {
return self::createContainsResolver($scope, $value, $subString);
},
'startsWith' => static function (Scope $scope, Arg $value, Arg $subString): array {
return self::createStartsWithResolver($scope, $value, $subString);
},
'endsWith' => static function (Scope $scope, Arg $value, Arg $subString): array {
return self::createEndsWithResolver($scope, $value, $subString);
},
];

foreach (['contains', 'startsWith', 'endsWith'] as $name) {
$this->resolvers[$name] = function (Scope $scope, Arg $value, Arg $subString) use ($name): array {
if ($scope->getType($subString->value)->isNonEmptyString()->yes()) {
return self::createIsNonEmptyStringAndSomethingExprPair($name, [$value, $subString]);
}

return [$this->resolvers['string']($scope, $value), null];
};
}

$assertionsResultingAtLeastInNonEmptyString = [
'startsWithLetter',
'unicodeLetters',
Expand Down Expand Up @@ -1022,4 +1021,138 @@ private function specifyRootExprIfSet(?Expr $rootExpr, SpecifiedTypes $specified
);
}

/**
* @return array{Expr, Expr}
*/
private static function createContainsResolver(Scope $scope, Arg $value, Arg $subString): array
{
$stringExpr = $scope->getType($subString->value)->isNonEmptyString()->yes()
? new BooleanAnd(
new FuncCall(
new Name('is_string'),
[$value]
),
new NotIdentical(
$value->value,
new String_('')
)
)
: new FuncCall(
new Name('is_string'),
[$value]
);

$expr = new BooleanOr(
$stringExpr,
new BooleanAnd(
$stringExpr,
new GreaterOrEqual(
new FuncCall(
new Name('strpos'),
[$value]
),
new LNumber(0)
)
)
);

$rootExpr = new BooleanAnd(
$expr,
new FuncCall(new Name('FAUX_FUNCTION_ contains'), [$value, $subString])
);

return [$expr, $rootExpr];
}

/**
* @return array{Expr, Expr}
*/
private static function createStartsWithResolver(Scope $scope, Arg $string, Arg $subString): array
{
$stringExpr = $scope->getType($subString->value)->isNonEmptyString()->yes()
? new BooleanAnd(
new FuncCall(
new Name('is_string'),
[$string]
),
new NotIdentical(
$string->value,
new String_('')
)
)
: new FuncCall(
new Name('is_string'),
[$string]
);

$expr = new BooleanOr(
$stringExpr,
new Identical(
new FuncCall(
new Name('strpos'),
[$string, $subString]
),
new LNumber(0)
)
);

$rootExpr = new BooleanAnd(
$expr,
new FuncCall(new Name('FAUX_FUNCTION_ startsWith'), [$string, $subString])
);

return [$expr, $rootExpr];
}

/**
* @return array{Expr, Expr}
*/
private static function createEndsWithResolver(Scope $scope, Arg $value, Arg $subString): array
{
if ($scope->getType($subString->value)->isNonEmptyString()->yes()) {
$stringExpr = new BooleanAnd(
new FuncCall(
new Name('is_string'),
[$value]
),
new NotIdentical(
$value->value,
new String_('')
)
);
} else {
$stringExpr = new FuncCall(
new Name('is_string'),
[$value]
);
}

$expr = new BooleanOr(
$stringExpr,
new Identical(
new FuncCall(
new Name('strpos'),
[$value, $subString]
),
new BinaryOp\Minus(
new FuncCall(
new Name('strlen'),
[$value]
),
new FuncCall(
new Name('strlen'),
[$subString]
)
)
)
);

$rootExpr = new BooleanAnd(
$expr,
new FuncCall(new Name('FAUX_FUNCTION_ endsWith'), [$value, $subString])
);

return [$expr, $rootExpr];
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ public function testExtension(): void
'Call to static method Webmozart\Assert\Assert::isInstanceOf() with Exception and class-string<Exception> will always evaluate to true.',
119,
],
[
'Call to static method Webmozart\Assert\Assert::startsWith() with \'value\' and string will always evaluate to true.',
126,
],
]);
}

Expand Down
8 changes: 8 additions & 0 deletions tests/Type/WebMozartAssert/data/impossible-check.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ public function testInstanceOfClassString(\Exception $e, string $name): void
Assert::isInstanceOf($e, $name);
}

public function testStartsWith(string $a): void
{
Assert::startsWith("value", "val");
Assert::startsWith("value", $a);
Assert::startsWith("value", $a);
Assert::startsWith("value", "bix");
}

}

interface Bar {};
Expand Down
4 changes: 2 additions & 2 deletions tests/Type/WebMozartAssert/data/string.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function startsWith(string $a, string $b): void
assertType('string', $a);

Assert::startsWith($a, $b);
assertType('non-empty-string', $a);
assertType('string', $a);
}

public function startsWithLetter(string $a): void
Expand All @@ -47,7 +47,7 @@ public function endsWith(string $a, string $b): void
assertType('string', $a);

Assert::endsWith($a, $b);
assertType('non-empty-string', $a);
assertType('string', $a);
}

public function unicodeLetters($a): void
Expand Down