| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
There was a problem hiding this comment.
I checked the fixes against what assertEmpty() does at runtime, at head e7037550f. make tests and make phpstan pass. In five cases the fixed assertion behaves differently from the original. I ran each original and fixed line with real PHPUnit 9.6:
| Case | Original | Fixed | Original result | Fixed result |
|---|---|---|---|---|
| @param int $x, null at runtime | assertNotEmpty($x) | assertNotSame(0, $x) | fails | passes |
| @param non-falsy-string $x, '0' at runtime | assertNotEmpty($x) | assertNotSame('', $x) | fails | passes |
| ?\SimpleXMLElement $x, <a/> | assertEmpty($x) | assertNull($x) | passes | fails |
| bool $b | assertEmpty(actual: $b) | assertFalse(actual: $b) | passes | Error: Unknown named parameter $actual |
| int $i, 0 | assertEmpty(message: 'm', actual: $i) | assertSame('', message: 'm', actual: $i) | passes | fails |
The type comes from $scope->getType(), so a PHPDoc type decides the fix. In a test, the assertion is often what checks that the PHPDoc is right. The negated fixes then accept values that the original rejected (the first two rows). $scope->getNativeType() would avoid that. The non-falsy-string case would then not be fixed at all, but assertEmpty() on a non-falsy string can never pass anyway.
An empty SimpleXMLElement is empty for empty(), so assertNull() is not the same. ?object has the same problem, because the object can be a SimpleXMLElement at runtime. Fixing only when (new ObjectType(\SimpleXMLElement::class))->isSuperTypeOf(...) is no() for the type without null would keep ?\stdClass and skip both.
The rule reads getArgs()[0] as the asserted value and keeps the arguments as they are:
Skipping the fix when any argument is named would cover both.
The other fixes are equivalent for their types, including the extra message argument (assertCount(0, $array, 'message')). float, plain string and unions without null stay unfixed, as the description says.
The three Mutation Testing reds come from this PR. Infection reports 4 escaped mutants, which change ->yes() to !->no() on the isObject(), isBoolean(), isArray() and isInteger() checks. So the tests have no value for which one of these checks is a "maybe" and the fix must not apply. The other CI checks pass.
I only checked the parameter names in PHPUnit 9.6, not in 10 to 12.
Sorry, something went wrong.
There was a problem hiding this comment.
Automatic fixes can change assertion behavior for unpacked arguments, nullable object/scalar unions, and Countable objects.
Review effort: Balanced
Findings: 3
Adds automatic fixes to PHPStan’s discouraged empty-assertion rule, replacing supported cases with more specific PHPUnit assertions.
Changes:
| File | Description |
|---|---|
| tests/Rules/PHPUnit/data/assert-empty-is-discouraged-native-union.php.fixed | Expected unchanged union and named-argument assertions. |
| tests/Rules/PHPUnit/data/assert-empty-is-discouraged-native-union.php | Union and named-argument fixtures. |
| tests/Rules/PHPUnit/data/assert-empty-is-discouraged-fixable.php.fixed | Expected assertion replacements. |
| tests/Rules/PHPUnit/data/assert-empty-is-discouraged-fixable.php | Supported and unsupported type fixtures. |
| tests/Rules/PHPUnit/AssertEmptyIsDiscouragedRuleTest.php | Adds automatic-fix tests. |
| src/Rules/PHPUnit/AssertEmptyIsDiscouragedRule.php | Implements type-based assertion fixes. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Sorry, something went wrong.
There was a problem hiding this comment.
I checked b207aedaf again. The PHPDoc, SimpleXMLElement, named-argument and mutation points from my first review are fixed, and the tests, PHPStan and CI pass.
The nullable-object fix still changes results. assertEmpty() also treats an EmptyIterator and a Countable with a count of 0 as empty. isBuiltin() only looks at the declared class, and a subclass or an implementation can add either. I ran the rule's fix on these parameter types and then ran each original and fixed line with PHPUnit 9.6:
| Parameter | Value at runtime | Original | Fixed |
|---|---|---|---|
| ?Collection (user class, Countable) | count() is 0 | assertEmpty() passes | assertNull() fails |
| ?MyIterator (extends EmptyIterator) | new instance | passes | fails |
| ?MyArrayObject (extends ArrayObject) | new, empty | passes | fails |
| ?OpenParent (non-final class) | a Countable subclass, count 0 | passes | fails |
| ?Thing (interface) | a Countable implementation, count 0 | passes | fails |
| ?Foo (non-final class) | a Countable subclass, count 0 | assertNotEmpty() fails | assertNotNull() passes |
| ?FinalFoo (final user class) | new instance | assertEmpty() fails | assertNull() fails |
Only the final class gives the same result. So the null fix could apply only when each class is final, does not implement Countable, and has no builtin parent. That covers EmptyIterator, ArrayObject and SimpleXMLElement subclasses too.
Copilot's UserDefinedObject|int|null case does not reproduce. The rule leaves Foo|int|null as it is, because getObjectClassReflections() returns nothing when a union member is not an object.
I ran the cases with PHPUnit 9.6 only. The IsEmpty constraint has the same EmptyIterator and Countable checks in 10.5, 11.5 and 12.0.
Sorry, something went wrong.
There was a problem hiding this comment.
Nullable unions containing objects and scalars can receive automatic fixes that change assertion outcomes.
Review effort: Balanced
Findings: None
Sorry, something went wrong.
There was a problem hiding this comment.
Trusting PHPDoc finality allows nullable-object fixes that change assertion behavior for runtime subclasses.
Review effort: Balanced
Findings: None
In code that hasn't changed since last review
src/Rules/PHPUnit/AssertEmptyIsDiscouragedRule.php:94
isFinal() accepts PHPDoc @final, which does not prevent runtime subclasses. For a nullable class annotated this way, a subclass implementing Countable with count zero passes assertEmpty() but fails the generated assertNull(); the negated rewrite also changes behavior. Use isFinalByKeyword() to require native finality, consistent with this fixer's use of native types.
Sorry, something went wrong.
There was a problem hiding this comment.
I checked 89814b5d2 again, and the null fix now only rewrites cases that behave the same at runtime.
I ran the rule's fix on 13 nullable parameter types. It rewrites four of them to assertNull() or assertNotNull(): ?FinalFoo in both directions, FinalFoo|FinalBar|null and a nullable plain enum. I ran each original and fixed assertion with PHPUnit 9.6 against null, both final classes and the enum case. All results are the same.
It leaves the other nine unchanged:
The last three are the nullable unions with scalar members from Copilot's last overview, so that case does not occur. The first two are not in the test data, but the parent loop and implementsInterface() handle them.
The tests and PHPStan pass, and all CI checks are green. Performance: on the PR's three test data files with the rule enabled, both sides report 46 errors. Base took 0.90-0.96 s and this PR 0.95-0.97 s over 5 interleaved runs, which is within noise.
Sorry, something went wrong.
|
Thank you! |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
closes #338
support scalar types. we skip all regular strings which are not non-falsey, because fixing these would yield ugly code like $s == '' || $s == '0'.
we only support the easiest cases for now which have a unambiguous fix and lead more readable code.