Skip to content

Commit b2e9fee

Browse files
committed
Improve assert for contains / startsWith / endsWith
1 parent ae758a2 commit b2e9fee

File tree

4 files changed

+157
-12
lines changed

4 files changed

+157
-12
lines changed

src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

Lines changed: 143 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -754,18 +754,17 @@ private function getExpressionResolvers(): array
754754
)
755755
);
756756
},
757+
'contains' => static function (Scope $scope, Arg $value, Arg $subString): array {
758+
return self::createContainsResolver($scope, $value, $subString);
759+
},
760+
'startsWith' => static function (Scope $scope, Arg $value, Arg $subString): array {
761+
return self::createStartsWithResolver($scope, $value, $subString);
762+
},
763+
'endsWith' => static function (Scope $scope, Arg $value, Arg $subString): array {
764+
return self::createEndsWithResolver($scope, $value, $subString);
765+
},
757766
];
758767

759-
foreach (['contains', 'startsWith', 'endsWith'] as $name) {
760-
$this->resolvers[$name] = function (Scope $scope, Arg $value, Arg $subString) use ($name): array {
761-
if ($scope->getType($subString->value)->isNonEmptyString()->yes()) {
762-
return self::createIsNonEmptyStringAndSomethingExprPair($name, [$value, $subString]);
763-
}
764-
765-
return [$this->resolvers['string']($scope, $value), null];
766-
};
767-
}
768-
769768
$assertionsResultingAtLeastInNonEmptyString = [
770769
'startsWithLetter',
771770
'unicodeLetters',
@@ -1022,4 +1021,138 @@ private function specifyRootExprIfSet(?Expr $rootExpr, SpecifiedTypes $specified
10221021
);
10231022
}
10241023

1024+
/**
1025+
* @return array{Expr, Expr}
1026+
*/
1027+
private static function createContainsResolver(Scope $scope, Arg $value, Arg $subString): array
1028+
{
1029+
$stringExpr = $scope->getType($subString->value)->isNonEmptyString()->yes()
1030+
? new BooleanAnd(
1031+
new FuncCall(
1032+
new Name('is_string'),
1033+
[$value]
1034+
),
1035+
new NotIdentical(
1036+
$value->value,
1037+
new String_('')
1038+
)
1039+
)
1040+
: new FuncCall(
1041+
new Name('is_string'),
1042+
[$value]
1043+
);
1044+
1045+
$expr = new BooleanOr(
1046+
$stringExpr,
1047+
new BooleanAnd(
1048+
$stringExpr,
1049+
new GreaterOrEqual(
1050+
new FuncCall(
1051+
new Name('strpos'),
1052+
[$value]
1053+
),
1054+
new LNumber(0)
1055+
)
1056+
)
1057+
);
1058+
1059+
$rootExpr = new BooleanAnd(
1060+
$expr,
1061+
new FuncCall(new Name('FAUX_FUNCTION_ contains'), [$value, $subString])
1062+
);
1063+
1064+
return [$expr, $rootExpr];
1065+
}
1066+
1067+
/**
1068+
* @return array{Expr, Expr}
1069+
*/
1070+
private static function createStartsWithResolver(Scope $scope, Arg $string, Arg $subString): array
1071+
{
1072+
$stringExpr = $scope->getType($subString->value)->isNonEmptyString()->yes()
1073+
? new BooleanAnd(
1074+
new FuncCall(
1075+
new Name('is_string'),
1076+
[$string]
1077+
),
1078+
new NotIdentical(
1079+
$string->value,
1080+
new String_('')
1081+
)
1082+
)
1083+
: new FuncCall(
1084+
new Name('is_string'),
1085+
[$string]
1086+
);
1087+
1088+
$expr = new BooleanOr(
1089+
$stringExpr,
1090+
new Identical(
1091+
new FuncCall(
1092+
new Name('strpos'),
1093+
[$string, $subString]
1094+
),
1095+
new LNumber(0)
1096+
)
1097+
);
1098+
1099+
$rootExpr = new BooleanAnd(
1100+
$expr,
1101+
new FuncCall(new Name('FAUX_FUNCTION_ startsWith'), [$string, $subString])
1102+
);
1103+
1104+
return [$expr, $rootExpr];
1105+
}
1106+
1107+
/**
1108+
* @return array{Expr, Expr}
1109+
*/
1110+
private static function createEndsWithResolver(Scope $scope, Arg $value, Arg $subString): array
1111+
{
1112+
if ($scope->getType($subString->value)->isNonEmptyString()->yes()) {
1113+
$stringExpr = new BooleanAnd(
1114+
new FuncCall(
1115+
new Name('is_string'),
1116+
[$value]
1117+
),
1118+
new NotIdentical(
1119+
$value->value,
1120+
new String_('')
1121+
)
1122+
);
1123+
} else {
1124+
$stringExpr = new FuncCall(
1125+
new Name('is_string'),
1126+
[$value]
1127+
);
1128+
}
1129+
1130+
$expr = new BooleanOr(
1131+
$stringExpr,
1132+
new Identical(
1133+
new FuncCall(
1134+
new Name('strpos'),
1135+
[$value, $subString]
1136+
),
1137+
new BinaryOp\Minus(
1138+
new FuncCall(
1139+
new Name('strlen'),
1140+
[$value]
1141+
),
1142+
new FuncCall(
1143+
new Name('strlen'),
1144+
[$subString]
1145+
)
1146+
)
1147+
)
1148+
);
1149+
1150+
$rootExpr = new BooleanAnd(
1151+
$expr,
1152+
new FuncCall(new Name('FAUX_FUNCTION_ endsWith'), [$value, $subString])
1153+
);
1154+
1155+
return [$expr, $rootExpr];
1156+
}
1157+
10251158
}

tests/Type/WebMozartAssert/ImpossibleCheckTypeMethodCallRuleTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ public function testExtension(): void
105105
'Call to static method Webmozart\Assert\Assert::isInstanceOf() with Exception and class-string<Exception> will always evaluate to true.',
106106
119,
107107
],
108+
[
109+
'Call to static method Webmozart\Assert\Assert::startsWith() with \'value\' and string will always evaluate to true.',
110+
126,
111+
],
108112
]);
109113
}
110114

tests/Type/WebMozartAssert/data/impossible-check.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ public function testInstanceOfClassString(\Exception $e, string $name): void
119119
Assert::isInstanceOf($e, $name);
120120
}
121121

122+
public function testStartsWith(string $a): void
123+
{
124+
Assert::startsWith("value", "val");
125+
Assert::startsWith("value", $a);
126+
Assert::startsWith("value", $a);
127+
Assert::startsWith("value", "bix");
128+
}
129+
122130
}
123131

124132
interface Bar {};

tests/Type/WebMozartAssert/data/string.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public function startsWith(string $a, string $b): void
2929
assertType('string', $a);
3030

3131
Assert::startsWith($a, $b);
32-
assertType('non-empty-string', $a);
32+
assertType('string', $a);
3333
}
3434

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

4949
Assert::endsWith($a, $b);
50-
assertType('non-empty-string', $a);
50+
assertType('string', $a);
5151
}
5252

5353
public function unicodeLetters($a): void

0 commit comments

Comments
 (0)